Java tutorial
/* * Copyright 2015 herd contributors * * 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.finra.herd.service.impl; import static org.finra.herd.model.dto.SearchIndexUpdateDto.SEARCH_INDEX_UPDATE_TYPE_UPDATE; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.finra.herd.core.HerdStringUtils; import org.finra.herd.dao.BusinessObjectDataDao; import org.finra.herd.dao.BusinessObjectDefinitionDao; import org.finra.herd.dao.BusinessObjectFormatDao; import org.finra.herd.dao.BusinessObjectFormatExternalInterfaceDao; import org.finra.herd.dao.config.DaoSpringModuleConfig; import org.finra.herd.model.annotation.NamespacePermission; import org.finra.herd.model.annotation.NamespacePermissions; import org.finra.herd.model.annotation.PublishNotificationMessages; import org.finra.herd.model.api.xml.Attribute; import org.finra.herd.model.api.xml.AttributeDefinition; import org.finra.herd.model.api.xml.BusinessObjectDefinitionKey; import org.finra.herd.model.api.xml.BusinessObjectFormat; import org.finra.herd.model.api.xml.BusinessObjectFormatAttributeDefinitionsUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatAttributesUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatCreateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatDdl; import org.finra.herd.model.api.xml.BusinessObjectFormatDdlCollectionRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatDdlCollectionResponse; import org.finra.herd.model.api.xml.BusinessObjectFormatDdlRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatKey; import org.finra.herd.model.api.xml.BusinessObjectFormatKeys; import org.finra.herd.model.api.xml.BusinessObjectFormatParentsUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatRetentionInformationUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatSchemaBackwardsCompatibilityUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatUpdateRequest; import org.finra.herd.model.api.xml.CustomDdlKey; import org.finra.herd.model.api.xml.NamespacePermissionEnum; import org.finra.herd.model.api.xml.Schema; import org.finra.herd.model.api.xml.SchemaColumn; import org.finra.herd.model.jpa.BusinessObjectDataAttributeDefinitionEntity; import org.finra.herd.model.jpa.BusinessObjectDefinitionEntity; import org.finra.herd.model.jpa.BusinessObjectFormatAttributeEntity; import org.finra.herd.model.jpa.BusinessObjectFormatEntity; import org.finra.herd.model.jpa.BusinessObjectFormatExternalInterfaceEntity; import org.finra.herd.model.jpa.CustomDdlEntity; import org.finra.herd.model.jpa.ExternalInterfaceEntity; import org.finra.herd.model.jpa.FileTypeEntity; import org.finra.herd.model.jpa.PartitionKeyGroupEntity; import org.finra.herd.model.jpa.RetentionTypeEntity; import org.finra.herd.model.jpa.SchemaColumnEntity; import org.finra.herd.service.BusinessObjectFormatService; import org.finra.herd.service.MessageNotificationEventService; import org.finra.herd.service.helper.AlternateKeyHelper; import org.finra.herd.service.helper.AttributeHelper; import org.finra.herd.service.helper.BusinessObjectDefinitionDaoHelper; import org.finra.herd.service.helper.BusinessObjectDefinitionHelper; import org.finra.herd.service.helper.BusinessObjectFormatDaoHelper; import org.finra.herd.service.helper.BusinessObjectFormatHelper; import org.finra.herd.service.helper.CustomDdlDaoHelper; import org.finra.herd.service.helper.DdlGenerator; import org.finra.herd.service.helper.DdlGeneratorFactory; import org.finra.herd.service.helper.FileTypeDaoHelper; import org.finra.herd.service.helper.PartitionKeyGroupDaoHelper; import org.finra.herd.service.helper.SearchIndexUpdateHelper; /** * The business object format service implementation. */ @Service @Transactional(value = DaoSpringModuleConfig.HERD_TRANSACTION_MANAGER_BEAN_NAME) public class BusinessObjectFormatServiceImpl implements BusinessObjectFormatService { private static final Logger LOGGER = LoggerFactory.getLogger(BusinessObjectFormatServiceImpl.class); private static final String SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG = "One or more schema column fields start with a prohibited character."; /** * List all schema column data types for which size increase is considered to be an additive schema change. */ public static final Set<String> SCHEMA_COLUMN_DATA_TYPES_WITH_ALLOWED_SIZE_INCREASE = Collections .unmodifiableSet(new HashSet<>(Arrays.asList("CHAR", "VARCHAR", "VARCHAR2"))); @Autowired private AlternateKeyHelper alternateKeyHelper; @Autowired private AttributeHelper attributeHelper; @Autowired private BusinessObjectDataDao businessObjectDataDao; @Autowired private BusinessObjectDefinitionDaoHelper businessObjectDefinitionDaoHelper; @Autowired private BusinessObjectDefinitionDao businessObjectDefinitionDao; @Autowired private BusinessObjectDefinitionHelper businessObjectDefinitionHelper; @Autowired private BusinessObjectFormatDao businessObjectFormatDao; @Autowired private BusinessObjectFormatExternalInterfaceDao businessObjectFormatExternalInterfaceDao; @Autowired private BusinessObjectFormatDaoHelper businessObjectFormatDaoHelper; @Autowired private BusinessObjectFormatHelper businessObjectFormatHelper; @Autowired private CustomDdlDaoHelper customDdlDaoHelper; @Autowired private DdlGeneratorFactory ddlGeneratorFactory; @Autowired private FileTypeDaoHelper fileTypeDaoHelper; @Autowired private MessageNotificationEventService messageNotificationEventService; @Autowired private PartitionKeyGroupDaoHelper partitionKeyGroupDaoHelper; @Autowired private SearchIndexUpdateHelper searchIndexUpdateHelper; @PublishNotificationMessages @NamespacePermission(fields = "#request.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat createBusinessObjectFormat(BusinessObjectFormatCreateRequest request) { // Perform the validation of the request parameters, except for the alternate key. validateBusinessObjectFormatCreateRequest(request); // Get business object format key from the request. BusinessObjectFormatKey businessObjectFormatKey = getBusinessObjectFormatKey(request); // Get the business object definition and ensure it exists. BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity( new BusinessObjectDefinitionKey(businessObjectFormatKey.getNamespace(), businessObjectFormatKey.getBusinessObjectDefinitionName())); // Get business object format file type and ensure it exists. FileTypeEntity fileTypeEntity = fileTypeDaoHelper .getFileTypeEntity(request.getBusinessObjectFormatFileType()); // Get the latest format version for this business format, if it exists. BusinessObjectFormatEntity latestVersionBusinessObjectFormatEntity = businessObjectFormatDao .getBusinessObjectFormatByAltKey(businessObjectFormatKey); // Check if the latest version exists. if (latestVersionBusinessObjectFormatEntity != null) { // Get the latest version business object format model object. BusinessObjectFormat latestVersionBusinessObjectFormat = businessObjectFormatHelper .createBusinessObjectFormatFromEntity(latestVersionBusinessObjectFormatEntity); // If the latest version format schema exists and allowNonBackwardsCompatibleChanges is not true, // then perform the additive schema validation and update the latest entity. if (latestVersionBusinessObjectFormat.getSchema() != null && BooleanUtils .isNotTrue(latestVersionBusinessObjectFormat.isAllowNonBackwardsCompatibleChanges())) { validateNewSchemaIsAdditiveToOldSchema(request.getSchema(), latestVersionBusinessObjectFormat.getSchema()); } // Update the latest entity. latestVersionBusinessObjectFormatEntity.setLatestVersion(false); businessObjectFormatDao.saveAndRefresh(latestVersionBusinessObjectFormatEntity); } // Create a business object format entity from the request information. Integer businessObjectFormatVersion = latestVersionBusinessObjectFormatEntity == null ? 0 : latestVersionBusinessObjectFormatEntity.getBusinessObjectFormatVersion() + 1; BusinessObjectFormatEntity newBusinessObjectFormatEntity = createBusinessObjectFormatEntity(request, businessObjectDefinitionEntity, fileTypeEntity, businessObjectFormatVersion, null); // Latest version format is the descriptive format for the bdef, update the bdef descriptive format to the newly created one if (latestVersionBusinessObjectFormatEntity != null && latestVersionBusinessObjectFormatEntity .equals(businessObjectDefinitionEntity.getDescriptiveBusinessObjectFormat())) { businessObjectDefinitionEntity.setDescriptiveBusinessObjectFormat(newBusinessObjectFormatEntity); businessObjectDefinitionDao.saveAndRefresh(businessObjectDefinitionEntity); } // Latest version business object exists, update its parents and children. // Latest version business object exists, carry the retention information to the new entity. if (latestVersionBusinessObjectFormatEntity != null) { // For each of the previous version's child, remove the parent link to the previous version and set the parent to the new version for (BusinessObjectFormatEntity childFormatEntity : latestVersionBusinessObjectFormatEntity .getBusinessObjectFormatChildren()) { childFormatEntity.getBusinessObjectFormatParents().remove(latestVersionBusinessObjectFormatEntity); childFormatEntity.getBusinessObjectFormatParents().add(newBusinessObjectFormatEntity); newBusinessObjectFormatEntity.getBusinessObjectFormatChildren().add(childFormatEntity); businessObjectFormatDao.saveAndRefresh(childFormatEntity); } // For each of the previous version's parent, remove the child link to the previous version and set the child link to the new version. for (BusinessObjectFormatEntity parentFormatEntity : latestVersionBusinessObjectFormatEntity .getBusinessObjectFormatParents()) { parentFormatEntity.getBusinessObjectFormatChildren() .remove(latestVersionBusinessObjectFormatEntity); parentFormatEntity.getBusinessObjectFormatChildren().add(newBusinessObjectFormatEntity); newBusinessObjectFormatEntity.getBusinessObjectFormatParents().add(parentFormatEntity); businessObjectFormatDao.saveAndRefresh(parentFormatEntity); } // Mark the latest version business object format. latestVersionBusinessObjectFormatEntity.setBusinessObjectFormatParents(null); latestVersionBusinessObjectFormatEntity.setBusinessObjectFormatChildren(null); // Carry the retention information from the latest entity to the new entity. newBusinessObjectFormatEntity .setRetentionPeriodInDays(latestVersionBusinessObjectFormatEntity.getRetentionPeriodInDays()); newBusinessObjectFormatEntity.setRecordFlag(latestVersionBusinessObjectFormatEntity.isRecordFlag()); newBusinessObjectFormatEntity .setRetentionType(latestVersionBusinessObjectFormatEntity.getRetentionType()); // Carry the schema backwards compatibility changes from the latest entity to the new entity. newBusinessObjectFormatEntity.setAllowNonBackwardsCompatibleChanges( latestVersionBusinessObjectFormatEntity.isAllowNonBackwardsCompatibleChanges()); // Carry external interface references from the latest entity to the new entity. for (BusinessObjectFormatExternalInterfaceEntity latestVersionBusinessObjectFormatExternalInterfaceEntity : latestVersionBusinessObjectFormatEntity .getBusinessObjectFormatExternalInterfaces()) { // Creates a business object format to external interface mapping entity. BusinessObjectFormatExternalInterfaceEntity businessObjectFormatExternalInterfaceEntity = new BusinessObjectFormatExternalInterfaceEntity(); newBusinessObjectFormatEntity.getBusinessObjectFormatExternalInterfaces() .add(businessObjectFormatExternalInterfaceEntity); businessObjectFormatExternalInterfaceEntity.setBusinessObjectFormat(newBusinessObjectFormatEntity); businessObjectFormatExternalInterfaceEntity.setExternalInterface( latestVersionBusinessObjectFormatExternalInterfaceEntity.getExternalInterface()); } businessObjectFormatDao.saveAndRefresh(newBusinessObjectFormatEntity); // Reset the retention information of the latest version business object format. latestVersionBusinessObjectFormatEntity.setRetentionType(null); latestVersionBusinessObjectFormatEntity.setRecordFlag(null); latestVersionBusinessObjectFormatEntity.setRetentionPeriodInDays(null); // Reset the schema backwards compatibility changes of the latest version business object format. latestVersionBusinessObjectFormatEntity.setAllowNonBackwardsCompatibleChanges(null); // Reset the external interface mappings of the latest version business object format. latestVersionBusinessObjectFormatEntity.getBusinessObjectFormatExternalInterfaces().clear(); businessObjectFormatDao.saveAndRefresh(latestVersionBusinessObjectFormatEntity); } // Notify the search index that a business object definition must be updated. LOGGER.info( "Modify the business object definition in the search index associated with the business object definition format being created." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_UPDATE); // Create a version change notification to be sent on create business object format event. messageNotificationEventService.processBusinessObjectFormatVersionChangeNotificationEvent( businessObjectFormatHelper.getBusinessObjectFormatKey(newBusinessObjectFormatEntity), latestVersionBusinessObjectFormatEntity != null ? latestVersionBusinessObjectFormatEntity.getBusinessObjectFormatVersion().toString() : ""); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(newBusinessObjectFormatEntity); } @PublishNotificationMessages @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat updateBusinessObjectFormat(BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatUpdateRequest request) { // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey); // Validate optional attributes. This is also going to trim the attribute names. attributeHelper.validateFormatAttributes(request.getAttributes()); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); // Update business object format description. businessObjectFormatEntity.setDescription(request.getDescription()); // Update business object format document schema. businessObjectFormatEntity.setDocumentSchema(getTrimmedString(request.getDocumentSchema())); // Update business object format document schema url. businessObjectFormatEntity.setDocumentSchemaUrl(getTrimmedString(request.getDocumentSchemaUrl())); // Validate optional schema information. This is also going to trim the relative schema column field values. validateBusinessObjectFormatSchema(request.getSchema(), businessObjectFormatEntity.getPartitionKey()); // Update business object format attributes updateBusinessObjectFormatAttributesHelper(businessObjectFormatEntity, request.getAttributes()); // Get business object format model object. BusinessObjectFormat businessObjectFormat = businessObjectFormatHelper .createBusinessObjectFormatFromEntity(businessObjectFormatEntity); // Check if we need to update business object format schema information. if ((request.getSchema() != null && !request.getSchema().equals(businessObjectFormat.getSchema())) || (request.getSchema() == null && businessObjectFormat.getSchema() != null)) { // TODO: Check if we are allowed to update schema information for this business object format. //if (businessObjectFormat.getSchema() != null && herdDao.getBusinessObjectDataCount(businessObjectFormatKey) > 0L) //{ // throw new IllegalArgumentException(String // .format("Can not update schema information for a business object format that has an existing schema " + // "defined and business object data associated with it. Business object format: {%s}", // herdDaoHelper.businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity))); //} // Update schema information by clearing and setting the relative business object // format entity fields and by re-creating schema column entities. Please note that // this approach results in changing all schema column Id's which could have // ramifications down the road if other entities have relations to schema columns. // Also, performance will be slightly slower doing a bunch of deletes followed by a bunch // of inserts for what could otherwise be a single SQL statement if only one column was changed. // Nevertheless, the below approach results in a simpler code. // Removes business object format schema information from the business object format entity. clearBusinessObjectFormatSchema(businessObjectFormatEntity); // In order to avoid INSERT-then-DELETE, we need to flush the session before we add new schema column entities. businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Populates schema information within the business object format entity. populateBusinessObjectFormatSchema(businessObjectFormatEntity, request.getSchema()); } // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Notify the search index that a business object definition must be updated. LOGGER.info( "Modify the business object definition in the search index associated with the business object definition format being updated." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectFormatEntity.getBusinessObjectDefinition().getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex( businessObjectFormatEntity.getBusinessObjectDefinition(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); // Create a version change notification to be sent on create business object format event. messageNotificationEventService.processBusinessObjectFormatVersionChangeNotificationEvent( businessObjectFormatHelper.getBusinessObjectFormatKey(businessObjectFormatEntity), businessObjectFormatEntity.getBusinessObjectFormatVersion().toString()); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectFormat getBusinessObjectFormat(BusinessObjectFormatKey businessObjectFormatKey) { return getBusinessObjectFormatImpl(businessObjectFormatKey); } /** * Gets a business object format for the specified key. * * @param businessObjectFormatKey the business object format key * * @return the business object format */ protected BusinessObjectFormat getBusinessObjectFormatImpl(BusinessObjectFormatKey businessObjectFormatKey) { // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey, false); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); boolean checkLatestVersion = false; //need to check latest version if the format key does not have the version if (businessObjectFormatKey.getBusinessObjectFormatVersion() != null) { checkLatestVersion = true; } // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity, checkLatestVersion); } @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat deleteBusinessObjectFormat(BusinessObjectFormatKey businessObjectFormatKey) { // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); // Get the associated Business Object Definition so we can update the search index BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectFormatEntity .getBusinessObjectDefinition(); // Check if we are allowed to delete this business object format. if (businessObjectDataDao.getBusinessObjectDataCount(businessObjectFormatKey) > 0L) { throw new IllegalArgumentException(String.format( "Can not delete a business object format that has business object data associated with it. Business object format: {%s}", businessObjectFormatHelper .businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity))); } if (!businessObjectFormatEntity.getBusinessObjectFormatChildren().isEmpty()) { throw new IllegalArgumentException(String.format( "Can not delete a business object format that has children associated with it. Business object format: {%s}", businessObjectFormatHelper .businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity))); } // Create and return the business object format object from the deleted entity. BusinessObjectFormat deletedBusinessObjectFormat = businessObjectFormatHelper .createBusinessObjectFormatFromEntity(businessObjectFormatEntity); // Check if business object format being deleted is used as a descriptive format. if (businessObjectFormatEntity.equals( businessObjectFormatEntity.getBusinessObjectDefinition().getDescriptiveBusinessObjectFormat())) { businessObjectFormatEntity.getBusinessObjectDefinition().setDescriptiveBusinessObjectFormat(null); businessObjectDefinitionDao.saveAndRefresh(businessObjectFormatEntity.getBusinessObjectDefinition()); } // Get the external interface references for this business object format. List<ExternalInterfaceEntity> externalInterfaceEntities = new ArrayList<>(); for (BusinessObjectFormatExternalInterfaceEntity businessObjectFormatExternalInterfaceEntity : businessObjectFormatEntity .getBusinessObjectFormatExternalInterfaces()) { externalInterfaceEntities.add(businessObjectFormatExternalInterfaceEntity.getExternalInterface()); } // Delete this business object format. businessObjectFormatDao.delete(businessObjectFormatEntity); // If this business object format version is the latest, set the latest flag on the previous version of this object format, if it exists. if (businessObjectFormatEntity.getLatestVersion()) { // Get the maximum version for this business object format, if it exists. Integer maxBusinessObjectFormatVersion = businessObjectFormatDao .getBusinessObjectFormatMaxVersion(businessObjectFormatKey); if (maxBusinessObjectFormatVersion != null) { // Retrieve the previous version business object format entity. Since we successfully got the maximum // version for this business object format, the retrieved entity is not expected to be null. BusinessObjectFormatEntity previousVersionBusinessObjectFormatEntity = businessObjectFormatDao .getBusinessObjectFormatByAltKey( new BusinessObjectFormatKey(businessObjectFormatKey.getNamespace(), businessObjectFormatKey.getBusinessObjectDefinitionName(), businessObjectFormatKey.getBusinessObjectFormatUsage(), businessObjectFormatKey.getBusinessObjectFormatFileType(), maxBusinessObjectFormatVersion)); // Update the previous version business object format entity. previousVersionBusinessObjectFormatEntity.setLatestVersion(true); // Update the previous version retention information previousVersionBusinessObjectFormatEntity.setRecordFlag(businessObjectFormatEntity.isRecordFlag()); previousVersionBusinessObjectFormatEntity .setRetentionPeriodInDays(businessObjectFormatEntity.getRetentionPeriodInDays()); previousVersionBusinessObjectFormatEntity .setRetentionType(businessObjectFormatEntity.getRetentionType()); // Update the previous version schema compatibility changes information. previousVersionBusinessObjectFormatEntity.setAllowNonBackwardsCompatibleChanges( businessObjectFormatEntity.isAllowNonBackwardsCompatibleChanges()); // Create external interface references for the previous version of the business object format. for (ExternalInterfaceEntity externalInterfaceEntity : externalInterfaceEntities) { // Creates a business object format to external interface mapping entity. BusinessObjectFormatExternalInterfaceEntity businessObjectFormatExternalInterfaceEntity = new BusinessObjectFormatExternalInterfaceEntity(); previousVersionBusinessObjectFormatEntity.getBusinessObjectFormatExternalInterfaces() .add(businessObjectFormatExternalInterfaceEntity); businessObjectFormatExternalInterfaceEntity .setBusinessObjectFormat(previousVersionBusinessObjectFormatEntity); businessObjectFormatExternalInterfaceEntity.setExternalInterface(externalInterfaceEntity); } // Save the updated entity. businessObjectFormatDao.saveAndRefresh(previousVersionBusinessObjectFormatEntity); } } // Notify the search index that a business object definition must be updated. LOGGER.info( "Modify the business object definition in the search index associated with the business object definition format being deleted." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_UPDATE); return deletedBusinessObjectFormat; } @Override public BusinessObjectFormatKeys getBusinessObjectFormats( BusinessObjectDefinitionKey businessObjectDefinitionKey, boolean latestBusinessObjectFormatVersion) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); // Ensure that a business object definition already exists with the specified name. businessObjectDefinitionDaoHelper.getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); // Gets the list of keys and return them. BusinessObjectFormatKeys businessObjectFormatKeys = new BusinessObjectFormatKeys(); businessObjectFormatKeys.getBusinessObjectFormatKeys().addAll(businessObjectFormatDao .getBusinessObjectFormats(businessObjectDefinitionKey, latestBusinessObjectFormatVersion)); return businessObjectFormatKeys; } @Override public BusinessObjectFormatKeys getBusinessObjectFormatsWithFilters( BusinessObjectDefinitionKey businessObjectDefinitionKey, String businessObjectFormatUsage, boolean latestBusinessObjectFormatVersion) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); // Perform Validation and trim of businessObjectFormatUsage businessObjectFormatUsage = alternateKeyHelper.validateStringParameter("business object format usage", businessObjectFormatUsage); // Ensure that a business object definition already exists with the specified name. businessObjectDefinitionDaoHelper.getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); // Gets the list of keys and return them. BusinessObjectFormatKeys businessObjectFormatKeys = new BusinessObjectFormatKeys(); businessObjectFormatKeys.getBusinessObjectFormatKeys() .addAll(businessObjectFormatDao.getBusinessObjectFormatsWithFilters(businessObjectDefinitionKey, businessObjectFormatUsage, latestBusinessObjectFormatVersion)); return businessObjectFormatKeys; } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectFormatDdl generateBusinessObjectFormatDdl(BusinessObjectFormatDdlRequest request) { return generateBusinessObjectFormatDdlImpl(request, false); } @NamespacePermission(fields = "#request?.businessObjectFormatDdlRequests?.![namespace]", permissions = NamespacePermissionEnum.READ) @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectFormatDdlCollectionResponse generateBusinessObjectFormatDdlCollection( BusinessObjectFormatDdlCollectionRequest request) { return generateBusinessObjectFormatDdlCollectionImpl(request); } @NamespacePermissions({ @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE), @NamespacePermission(fields = "#businessObjectFormatParentsUpdateRequest?.businessObjectFormatParents?.![namespace]", permissions = NamespacePermissionEnum.READ) }) @Override public BusinessObjectFormat updateBusinessObjectFormatParents(BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatParentsUpdateRequest businessObjectFormatParentsUpdateRequest) { Assert.notNull(businessObjectFormatParentsUpdateRequest, "A Business Object Format Parents Update Request is required."); // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey, false); Assert.isNull(businessObjectFormatKey.getBusinessObjectFormatVersion(), "Business object format version must not be specified."); // Perform validation and trim for the business object format parents validateBusinessObjectFormatParents( businessObjectFormatParentsUpdateRequest.getBusinessObjectFormatParents()); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); // Retrieve and ensure that business object format parents exist. A set is used to ignore duplicate business object format parents. Set<BusinessObjectFormatEntity> businessObjectFormatParents = new HashSet<>(); for (BusinessObjectFormatKey businessObjectFormatParent : businessObjectFormatParentsUpdateRequest .getBusinessObjectFormatParents()) { // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntityParent = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatParent); businessObjectFormatParents.add(businessObjectFormatEntityParent); } // Set the business object format parents. businessObjectFormatEntity.setBusinessObjectFormatParents(new ArrayList<>(businessObjectFormatParents)); // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Notify the search index that a business object definition must be updated. LOGGER.info( "Modify the business object definition in the search index associated with the business object definition format being updated." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectFormatEntity.getBusinessObjectDefinition().getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex( businessObjectFormatEntity.getBusinessObjectDefinition(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat updateBusinessObjectFormatAttributes( BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatAttributesUpdateRequest businessObjectFormatAttributesUpdateRequest) { // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey); Assert.notNull(businessObjectFormatAttributesUpdateRequest, "A business object format attributes update request is required."); Assert.notNull(businessObjectFormatAttributesUpdateRequest.getAttributes(), "A business object format attributes list is required."); List<Attribute> attributes = businessObjectFormatAttributesUpdateRequest.getAttributes(); // Validate optional attributes. This is also going to trim the attribute names. attributeHelper.validateFormatAttributes(attributes); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); // Update the business object format attributes updateBusinessObjectFormatAttributesHelper(businessObjectFormatEntity, attributes); // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat updateBusinessObjectFormatAttributeDefinitions( BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatAttributeDefinitionsUpdateRequest businessObjectFormatAttributeDefinitionsUpdateRequest) { // Perform validation and trim the alternate key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey); Assert.notNull(businessObjectFormatAttributeDefinitionsUpdateRequest, "A business object format attribute definitions update request is required."); List<AttributeDefinition> attributeDefinitions = businessObjectFormatAttributeDefinitionsUpdateRequest .getAttributeDefinitions(); // Validate and trim optional attribute definitions. This is also going to trim the attribute definition names. validateAndTrimBusinessObjectFormatAttributeDefinitionsHelper(attributeDefinitions); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); // Update the business object format attributes updateBusinessObjectFormatAttributeDefinitionsHelper(businessObjectFormatEntity, attributeDefinitions); // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } /** * Retrieves the DDL to initialize the specified type of the database system (e.g. Hive) by creating a table for the requested business object format. * * @param request the business object format DDL request * @param skipRequestValidation specifies whether to skip the request validation and trimming * * @return the business object format DDL information */ protected BusinessObjectFormatDdl generateBusinessObjectFormatDdlImpl(BusinessObjectFormatDdlRequest request, boolean skipRequestValidation) { // Perform the validation. if (!skipRequestValidation) { validateBusinessObjectFormatDdlRequest(request); } // Get the business object format entity for the specified parameters and make sure it exists. // Please note that when format version is not specified, we should get back the latest format version. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(new BusinessObjectFormatKey(request.getNamespace(), request.getBusinessObjectDefinitionName(), request.getBusinessObjectFormatUsage(), request.getBusinessObjectFormatFileType(), request.getBusinessObjectFormatVersion())); // Get business object format key. BusinessObjectFormatKey businessObjectFormatKey = businessObjectFormatHelper .getBusinessObjectFormatKey(businessObjectFormatEntity); // Validate that format has schema information. Assert.notEmpty(businessObjectFormatEntity.getSchemaColumns(), String.format( "Business object format with namespace \"%s\", business object definition name \"%s\", format usage \"%s\", format file type \"%s\"," + " and format version \"%s\" doesn't have schema information.", businessObjectFormatKey.getNamespace(), businessObjectFormatKey.getBusinessObjectDefinitionName(), businessObjectFormatKey.getBusinessObjectFormatUsage(), businessObjectFormatKey.getBusinessObjectFormatFileType(), businessObjectFormatKey.getBusinessObjectFormatVersion())); // If it was specified, retrieve the custom DDL and ensure it exists. CustomDdlEntity customDdlEntity = null; if (StringUtils.isNotBlank(request.getCustomDdlName())) { customDdlEntity = customDdlDaoHelper .getCustomDdlEntity(new CustomDdlKey(businessObjectFormatKey.getNamespace(), businessObjectFormatKey.getBusinessObjectDefinitionName(), businessObjectFormatKey.getBusinessObjectFormatUsage(), businessObjectFormatKey.getBusinessObjectFormatFileType(), businessObjectFormatKey.getBusinessObjectFormatVersion(), request.getCustomDdlName())); } // Create business object format DDL object instance. BusinessObjectFormatDdl businessObjectFormatDdl = new BusinessObjectFormatDdl(); businessObjectFormatDdl.setNamespace(businessObjectFormatKey.getNamespace()); businessObjectFormatDdl .setBusinessObjectDefinitionName(businessObjectFormatKey.getBusinessObjectDefinitionName()); businessObjectFormatDdl .setBusinessObjectFormatUsage(businessObjectFormatKey.getBusinessObjectFormatUsage()); businessObjectFormatDdl .setBusinessObjectFormatFileType(businessObjectFormatKey.getBusinessObjectFormatFileType()); businessObjectFormatDdl .setBusinessObjectFormatVersion(businessObjectFormatKey.getBusinessObjectFormatVersion()); businessObjectFormatDdl.setOutputFormat(request.getOutputFormat()); businessObjectFormatDdl.setTableName(request.getTableName()); businessObjectFormatDdl.setCustomDdlName( customDdlEntity != null ? customDdlEntity.getCustomDdlName() : request.getCustomDdlName()); DdlGenerator ddlGenerator = ddlGeneratorFactory.getDdlGenerator(request.getOutputFormat()); String ddl; if (Boolean.TRUE.equals(request.isReplaceColumns())) { ddl = ddlGenerator.generateReplaceColumnsStatement(request, businessObjectFormatEntity); } else { ddl = ddlGenerator.generateCreateTableDdl(request, businessObjectFormatEntity, customDdlEntity); } businessObjectFormatDdl.setDdl(ddl); // Return business object format DDL. return businessObjectFormatDdl; } /** * Retrieves the DDL to initialize the specified type of the database system (e.g. Hive) by creating tables for a collection of business object formats. * * @param businessObjectFormatDdlCollectionRequest the business object format DDL collection request * * @return the business object format DDL information */ protected BusinessObjectFormatDdlCollectionResponse generateBusinessObjectFormatDdlCollectionImpl( BusinessObjectFormatDdlCollectionRequest businessObjectFormatDdlCollectionRequest) { // Perform the validation of the entire request, before we start processing the individual requests that requires the database access. validateBusinessObjectFormatDdlCollectionRequest(businessObjectFormatDdlCollectionRequest); // Process the individual requests and build the response. BusinessObjectFormatDdlCollectionResponse businessObjectFormatDdlCollectionResponse = new BusinessObjectFormatDdlCollectionResponse(); List<BusinessObjectFormatDdl> businessObjectFormatDdlResponses = new ArrayList<>(); businessObjectFormatDdlCollectionResponse .setBusinessObjectFormatDdlResponses(businessObjectFormatDdlResponses); List<String> ddls = new ArrayList<>(); for (BusinessObjectFormatDdlRequest request : businessObjectFormatDdlCollectionRequest .getBusinessObjectFormatDdlRequests()) { // Please note that when calling to process individual ddl requests, we ask to skip the request validation and trimming step. BusinessObjectFormatDdl businessObjectFormatDdl = generateBusinessObjectFormatDdlImpl(request, true); businessObjectFormatDdlResponses.add(businessObjectFormatDdl); ddls.add(businessObjectFormatDdl.getDdl()); } businessObjectFormatDdlCollectionResponse.setDdlCollection(StringUtils.join(ddls, "\n\n")); return businessObjectFormatDdlCollectionResponse; } @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat updateBusinessObjectFormatRetentionInformation( BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatRetentionInformationUpdateRequest updateRequest) { // Validate and trim the business object format retention information update request update request. Assert.notNull(updateRequest, "A business object format retention information update request must be specified."); Assert.notNull(updateRequest.isRecordFlag(), "A record flag in business object format retention information update request must be specified."); businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey, false); Assert.isNull(businessObjectFormatKey.getBusinessObjectFormatVersion(), "Business object format version must not be specified."); // Validate business object format retention information. RetentionTypeEntity recordRetentionTypeEntity = null; if (updateRequest.getRetentionType() != null) { // Trim the business object format retention in update request. updateRequest.setRetentionType(updateRequest.getRetentionType().trim()); // Retrieve and ensure that a retention type exists. recordRetentionTypeEntity = businessObjectFormatDaoHelper .getRecordRetentionTypeEntity(updateRequest.getRetentionType()); if (recordRetentionTypeEntity.getCode().equals(RetentionTypeEntity.PARTITION_VALUE)) { Assert.notNull(updateRequest.getRetentionPeriodInDays(), String.format("A retention period in days must be specified for %s retention type.", RetentionTypeEntity.PARTITION_VALUE)); Assert.isTrue(updateRequest.getRetentionPeriodInDays() > 0, String.format( "A positive retention period in days must be specified for %s retention type.", RetentionTypeEntity.PARTITION_VALUE)); } else { Assert.isNull(updateRequest.getRetentionPeriodInDays(), String.format("A retention period in days cannot be specified for %s retention type.", recordRetentionTypeEntity.getCode())); } } else { // Do not allow retention period to be specified without retention type. Assert.isNull(updateRequest.getRetentionPeriodInDays(), "A retention period in days cannot be specified without retention type."); } // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); businessObjectFormatEntity.setRecordFlag(BooleanUtils.isTrue(updateRequest.isRecordFlag())); businessObjectFormatEntity.setRetentionPeriodInDays(updateRequest.getRetentionPeriodInDays()); businessObjectFormatEntity.setRetentionType(recordRetentionTypeEntity); // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } @NamespacePermission(fields = "#businessObjectFormatKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override public BusinessObjectFormat updateBusinessObjectFormatSchemaBackwardsCompatibilityChanges( BusinessObjectFormatKey businessObjectFormatKey, BusinessObjectFormatSchemaBackwardsCompatibilityUpdateRequest businessObjectFormatSchemaBackwardsCompatibilityUpdateRequest) { // Validate business object format schema backwards compatibility changes update request. Assert.notNull(businessObjectFormatSchemaBackwardsCompatibilityUpdateRequest, "A business object format schema backwards compatibility changes update request must be specified."); Assert.notNull( businessObjectFormatSchemaBackwardsCompatibilityUpdateRequest .isAllowNonBackwardsCompatibleChanges(), "allowNonBackwardsCompatibleChanges flag in business object format schema backwards compatibility changes update request must be specified."); // Validate and trim the business object format key parameters. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey, false); Assert.isNull(businessObjectFormatKey.getBusinessObjectFormatVersion(), "Business object format version must not be specified."); // Retrieve and ensure that a business object format exists. BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); businessObjectFormatEntity.setAllowNonBackwardsCompatibleChanges( BooleanUtils.isTrue(businessObjectFormatSchemaBackwardsCompatibilityUpdateRequest .isAllowNonBackwardsCompatibleChanges())); // Persist and refresh the entity. businessObjectFormatEntity = businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); // Create and return the business object format object from the persisted entity. return businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity); } /** * Validates the business object format create request, except for the alternate key values. This method also trims request parameters. * * @param request the business object format create request * * @throws IllegalArgumentException if any validation errors were found */ private void validateBusinessObjectFormatCreateRequest(BusinessObjectFormatCreateRequest request) { // Extract business object format key from the create request. BusinessObjectFormatKey businessObjectFormatKey = getBusinessObjectFormatKey(request); // Perform validation and trim the business object format key parameters, except for a business object format version. businessObjectFormatHelper.validateBusinessObjectFormatKey(businessObjectFormatKey, false); // Update the request object instance with the alternate key parameters. updateBusinessObjectFormatAlternateKeyOnCreateRequest(request, businessObjectFormatKey); // Perform validation of the partition key. This method also trims the partition key value. request.setPartitionKey( alternateKeyHelper.validateStringParameter("partition key", request.getPartitionKey())); // Validate attributes. attributeHelper.validateFormatAttributes(request.getAttributes()); // Validate attribute definitions if they are specified. if (!CollectionUtils.isEmpty(request.getAttributeDefinitions())) { Map<String, AttributeDefinition> attributeNameValidationMap = new HashMap<>(); for (AttributeDefinition attributeDefinition : request.getAttributeDefinitions()) { Assert.hasText(attributeDefinition.getName(), "An attribute definition name must be specified."); attributeDefinition.setName(attributeDefinition.getName().trim()); // Ensure the attribute key isn't a duplicate by using a map with a "lowercase" name as the key for case insensitivity. String lowercaseAttributeDefinitionName = attributeDefinition.getName().toLowerCase(); if (attributeNameValidationMap.containsKey(lowercaseAttributeDefinitionName)) { throw new IllegalArgumentException(String.format( "Duplicate attribute definition name \"%s\" found.", attributeDefinition.getName())); } attributeNameValidationMap.put(lowercaseAttributeDefinitionName, attributeDefinition); } } // Validate optional schema information. validateBusinessObjectFormatSchema(request.getSchema(), request.getPartitionKey()); } /** * Validate the business object format parents * * @param businessObjectFormatParents business object format parents */ private void validateBusinessObjectFormatParents(List<BusinessObjectFormatKey> businessObjectFormatParents) { // Validate parents business object format. if (!CollectionUtils.isEmpty(businessObjectFormatParents)) { for (BusinessObjectFormatKey parentBusinessObjectFormatKey : businessObjectFormatParents) { Assert.notNull(parentBusinessObjectFormatKey, "Parent object format must be specified."); businessObjectFormatHelper.validateBusinessObjectFormatKey(parentBusinessObjectFormatKey, false); Assert.isNull(parentBusinessObjectFormatKey.getBusinessObjectFormatVersion(), "Business object format version must not be specified."); } } } /** * Validates the business object format schema. This method also trims some of the schema column attributes parameters. * * @param schema the business object format schema * @param partitionKey the business object format partition key * * @throws IllegalArgumentException if any validation errors were found. */ private void validateBusinessObjectFormatSchema(Schema schema, String partitionKey) { // Validate optional schema information. if (schema != null) { // Perform validation. Assert.notNull(schema.getNullValue(), "A schema null value can not be null."); // Remove leading and trailing spaces. // Note that we don't trim the row option characters since they could contain white space or non-ASCII characters which would get removed if // "trim"ed. schema.setPartitionKeyGroup( schema.getPartitionKeyGroup() == null ? null : schema.getPartitionKeyGroup().trim()); Assert.isTrue(!CollectionUtils.isEmpty(schema.getColumns()), "A schema must have at least one column."); // Validates the schema columns. We are creating a map here since it is used across both data and partition columns. LinkedHashMap<String, SchemaColumn> schemaEqualityValidationMap = new LinkedHashMap<>(); validateSchemaColumns(schema.getColumns(), schemaEqualityValidationMap); validateSchemaColumns(schema.getPartitions(), schemaEqualityValidationMap); // Ensure the partition key matches the first schema column name if a single partition column is present. // TODO: We are doing this check since the primary partition is specified in 2 places and we want to keep them in sync. In the future, // the partition key will go away and this check will be removed. if (!CollectionUtils.isEmpty(schema.getPartitions())) { SchemaColumn schemaColumn = schema.getPartitions().get(0); if (!partitionKey.equalsIgnoreCase(schemaColumn.getName())) { throw new IllegalArgumentException(String.format( "Partition key \"%s\" does not match the first schema partition column name \"%s\".", partitionKey, schemaColumn.getName())); } } } } /** * Validates a list of schema columns. * * @param schemaColumns the list of schema columns. * @param schemaEqualityValidationMap a map of schema column names to their schema column. This is used to check equality across all data schema columns as * well as partition schema columns. */ private void validateSchemaColumns(List<SchemaColumn> schemaColumns, LinkedHashMap<String, SchemaColumn> schemaEqualityValidationMap) { // Validate schema columns if they are specified. if (!CollectionUtils.isEmpty(schemaColumns)) { // Create a schema column name map that we will use to check for duplicate // columns for the specified list of schema columns (i.e. data or partition). LinkedHashMap<String, SchemaColumn> schemaColumnNameValidationMap = new LinkedHashMap<>(); // Loop through each schema column in the list. for (SchemaColumn schemaColumn : schemaColumns) { // Perform validation. Assert.hasText(schemaColumn.getName(), "A schema column name must be specified."); Assert.hasText(schemaColumn.getType(), "A schema column data type must be specified."); // Remove leading and trailing spaces. schemaColumn.setName(schemaColumn.getName().trim()); schemaColumn.setType(schemaColumn.getType().trim()); schemaColumn.setSize(schemaColumn.getSize() == null ? null : schemaColumn.getSize().trim()); schemaColumn.setDefaultValue( schemaColumn.getDefaultValue() == null ? null : schemaColumn.getDefaultValue().trim()); // Check if schema column is prone to CSV Injection attack checkSchemaColumnCsvInjection(schemaColumn); // Ensure the column name isn't a duplicate within this list only by using a map. String lowercaseSchemaColumnName = schemaColumn.getName().toLowerCase(); if (schemaColumnNameValidationMap.containsKey(lowercaseSchemaColumnName)) { throw new IllegalArgumentException( String.format("Duplicate schema column name \"%s\" found.", schemaColumn.getName())); } schemaColumnNameValidationMap.put(lowercaseSchemaColumnName, schemaColumn); // Ensure a partition column and a data column are equal (i.e. contain the same configuration). SchemaColumn existingSchemaColumn = schemaEqualityValidationMap.get(lowercaseSchemaColumnName); if ((existingSchemaColumn != null) && !schemaColumn.equals(existingSchemaColumn)) { throw new IllegalArgumentException( "Schema data and partition column configurations with name \"" + schemaColumn.getName() + "\" have conflicting values. All column values are case sensitive and must be identical."); } schemaEqualityValidationMap.put(lowercaseSchemaColumnName, schemaColumn); } } } /** * check if a schema column is prone to CSV Injection attack * * @param schemaColumn schema column */ private void checkSchemaColumnCsvInjection(SchemaColumn schemaColumn) { HerdStringUtils.checkCsvInjection(schemaColumn.getName(), SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG); HerdStringUtils.checkCsvInjection(schemaColumn.getType(), SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG); HerdStringUtils.checkCsvInjection(schemaColumn.getSize(), SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG); HerdStringUtils.checkCsvInjection(schemaColumn.getDefaultValue(), SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG); HerdStringUtils.checkCsvInjection(schemaColumn.getDescription(), SCHEMA_COLUMN_CSV_INJECTION_ERROR_MSG); } /** * Validates that the new business object format version schema is additive to the old one. * * @param newSchema the new schema * @param oldSchema the old schema */ private void validateNewSchemaIsAdditiveToOldSchema(Schema newSchema, Schema oldSchema) { String mainErrorMessage = "New format version schema is not \"additive\" to the previous format version schema."; // Validate that new version format schema is specified. Assert.notNull(newSchema, String.format("%s New format version schema is not specified.", mainErrorMessage)); // Validate that there are no changes to the schema null value, which is a required parameter. // Please note that NULL value in the database represents an empty string. Assert.isTrue( oldSchema.getNullValue() == null ? newSchema.getNullValue().isEmpty() : oldSchema.getNullValue().equals(newSchema.getNullValue()), String.format( "%s New format version null value does not match to the previous format version null value.", mainErrorMessage)); // Validate that there are no changes to the delimiter character, which is a an optional parameter. // Please note that null and an empty string values are both stored in the database as NULL. Assert.isTrue( oldSchema.getDelimiter() == null ? newSchema.getDelimiter() == null || newSchema.getDelimiter().isEmpty() : oldSchema.getDelimiter().equals(newSchema.getDelimiter()), String.format( "%s New format version delimiter character does not match to the previous format version delimiter character.", mainErrorMessage)); // Validate that there are no changes to the collection items delimiter character, which is a an optional parameter. // Please note that null and an empty string values are both stored in the database as NULL. Assert.isTrue( oldSchema.getCollectionItemsDelimiter() == null ? newSchema.getCollectionItemsDelimiter() == null || newSchema.getCollectionItemsDelimiter().isEmpty() : oldSchema.getCollectionItemsDelimiter().equals(newSchema.getCollectionItemsDelimiter()), String.format( "%s New format version collection items delimiter character does not match to the previous format version collection items delimiter character.", mainErrorMessage)); // Validate that there are no changes to the map keys delimiter character, which is a an optional parameter. // Please note that null and an empty string values are both stored in the database as NULL. Assert.isTrue( oldSchema.getMapKeysDelimiter() == null ? newSchema.getMapKeysDelimiter() == null || newSchema.getMapKeysDelimiter().isEmpty() : oldSchema.getMapKeysDelimiter().equals(newSchema.getMapKeysDelimiter()), String.format( "%s New format version map keys delimiter character does not match to the previous format version map keys delimiter character.", mainErrorMessage)); // Validate that there are no changes to the escape character, which is a an optional parameter. // Please note that null and an empty string values are both stored in the database as NULL. Assert.isTrue( oldSchema.getEscapeCharacter() == null ? newSchema.getEscapeCharacter() == null || newSchema.getEscapeCharacter().isEmpty() : oldSchema.getEscapeCharacter().equals(newSchema.getEscapeCharacter()), String.format( "%s New format version escape character does not match to the previous format version escape character.", mainErrorMessage)); // Validate that there are no non-additive changes to partition columns. Assert.isTrue( validateNewSchemaColumnsAreAdditiveToOldSchemaColumns(newSchema.getPartitions(), oldSchema.getPartitions()), String.format("%s Non-additive changes detected to the previously defined partition columns.", mainErrorMessage)); // Validate that there are no non-additive changes to the previous format version regular columns. // No null check is needed on schema columns, since at least one column is required if format schema is specified. Assert.isTrue((oldSchema.getColumns().size() <= newSchema.getColumns().size()) && validateNewSchemaColumnsAreAdditiveToOldSchemaColumns( newSchema.getColumns().subList(0, oldSchema.getColumns().size()), oldSchema.getColumns()), String.format( "%s Non-additive changes detected to the previously defined regular (non-partitioning) columns.", mainErrorMessage)); } /** * Validate that a new list of schema columns is additive to an old one. * * @param newSchemaColumns the list of schema columns from the new schema * @param oldSchemaColumns the list of schema columns from the old schema * * @return true if schema columns are additive, false otherwise */ private boolean validateNewSchemaColumnsAreAdditiveToOldSchemaColumns(List<SchemaColumn> newSchemaColumns, List<SchemaColumn> oldSchemaColumns) { // Null check is required for the new and old list of columns, since partition columns are optional in format schema. if (newSchemaColumns == null || oldSchemaColumns == null) { return (newSchemaColumns == null && oldSchemaColumns == null); } else if (newSchemaColumns.size() != oldSchemaColumns.size()) { return false; } else { // Loop through the schema columns and compare each pair of new and old columns. for (int i = 0; i < newSchemaColumns.size(); i++) { // Get instance copies for both new and old schema columns. // Null check is not needed here, since schema column instance can not be null. SchemaColumn newSchemaColumnCopy = (SchemaColumn) newSchemaColumns.get(i).clone(); SchemaColumn oldSchemaColumnCopy = (SchemaColumn) oldSchemaColumns.get(i).clone(); // Since we allow column description to be changed, set the description field to null for both columns. newSchemaColumnCopy.setDescription(null); oldSchemaColumnCopy.setDescription(null); // For a set of data types, size increase is considered to be an additive change. // Null check on schema column date type is not needed, since it is a required field for schema column. if (SCHEMA_COLUMN_DATA_TYPES_WITH_ALLOWED_SIZE_INCREASE .contains(newSchemaColumnCopy.getType().toUpperCase())) { // The below string to integer conversion methods return an int represented by the string, or zero if conversion fails. int newSize = NumberUtils.toInt(newSchemaColumnCopy.getSize()); int oldSize = NumberUtils.toInt(oldSchemaColumnCopy.getSize()); // If new size is greater than the old one and they are both positive integers, then set the size field to null for both columns. if (oldSize > 0 && newSize >= oldSize) { newSchemaColumnCopy.setSize(null); oldSchemaColumnCopy.setSize(null); } } // Fail the validation if columns do not match. if (!newSchemaColumnCopy.equals(oldSchemaColumnCopy)) { return false; } } return true; } } /** * Validates a business object format DDL collection request. This method also trims appropriate request parameters. * * @param businessObjectFormatDdlCollectionRequest the request * * @throws IllegalArgumentException if any validation errors were found */ private void validateBusinessObjectFormatDdlCollectionRequest( BusinessObjectFormatDdlCollectionRequest businessObjectFormatDdlCollectionRequest) { Assert.notNull(businessObjectFormatDdlCollectionRequest, "A business object format DDL collection request must be specified."); Assert.isTrue( !CollectionUtils .isEmpty(businessObjectFormatDdlCollectionRequest.getBusinessObjectFormatDdlRequests()), "At least one business object format DDL request must be specified."); for (BusinessObjectFormatDdlRequest request : businessObjectFormatDdlCollectionRequest .getBusinessObjectFormatDdlRequests()) { validateBusinessObjectFormatDdlRequest(request); } } /** * Validates the business object format DDL request. This method also trims appropriate request parameters. * * @param request the business object format DDL request * * @throws IllegalArgumentException if any validation errors were found */ private void validateBusinessObjectFormatDdlRequest(BusinessObjectFormatDdlRequest request) { Assert.notNull(request, "A business object format DDL request must be specified."); // Validate and trim the request parameters. Assert.hasText(request.getNamespace(), "A namespace must be specified."); request.setNamespace(request.getNamespace().trim()); Assert.hasText(request.getBusinessObjectDefinitionName(), "A business object definition name must be specified."); request.setBusinessObjectDefinitionName(request.getBusinessObjectDefinitionName().trim()); Assert.hasText(request.getBusinessObjectFormatUsage(), "A business object format usage must be specified."); request.setBusinessObjectFormatUsage(request.getBusinessObjectFormatUsage().trim()); Assert.hasText(request.getBusinessObjectFormatFileType(), "A business object format file type must be specified."); request.setBusinessObjectFormatFileType(request.getBusinessObjectFormatFileType().trim()); Assert.notNull(request.getOutputFormat(), "An output format must be specified."); Assert.hasText(request.getTableName(), "A table name must be specified."); request.setTableName(request.getTableName().trim()); if (BooleanUtils.isTrue(request.isReplaceColumns())) { Assert.isTrue(BooleanUtils.isNotTrue(request.isIncludeDropTableStatement()), "'includeDropTableStatement' must not be specified when 'replaceColumns' is true"); Assert.isTrue(BooleanUtils.isNotTrue(request.isIncludeIfNotExistsOption()), "'includeIfNotExistsOption' must not be specified when 'replaceColumns' is true"); Assert.isTrue(StringUtils.isBlank(request.getCustomDdlName()), "'customDdlName' must not be specified when 'replaceColumns' is true"); } if (request.getCustomDdlName() != null) { request.setCustomDdlName(request.getCustomDdlName().trim()); } } /** * Creates a business object format key from the relative values in the business object format create request. Please note that the key will contain a null * for the business object format version value. * * @param businessObjectFormatCreateRequest the business object format create request * * @return the business object format key */ private BusinessObjectFormatKey getBusinessObjectFormatKey( BusinessObjectFormatCreateRequest businessObjectFormatCreateRequest) { BusinessObjectFormatKey businessObjectFormatKey = new BusinessObjectFormatKey(); businessObjectFormatKey.setNamespace(businessObjectFormatCreateRequest.getNamespace()); businessObjectFormatKey.setBusinessObjectDefinitionName( businessObjectFormatCreateRequest.getBusinessObjectDefinitionName()); businessObjectFormatKey .setBusinessObjectFormatUsage(businessObjectFormatCreateRequest.getBusinessObjectFormatUsage()); businessObjectFormatKey.setBusinessObjectFormatFileType( businessObjectFormatCreateRequest.getBusinessObjectFormatFileType()); return businessObjectFormatKey; } /** * Sets the relative fields in the business object format create request per specified business object format alternate key. * * @param businessObjectFormatCreateRequest the business object format create request * @param businessObjectFormatKey the business object format alternate key */ private void updateBusinessObjectFormatAlternateKeyOnCreateRequest( BusinessObjectFormatCreateRequest businessObjectFormatCreateRequest, BusinessObjectFormatKey businessObjectFormatKey) { businessObjectFormatCreateRequest.setNamespace(businessObjectFormatKey.getNamespace()); businessObjectFormatCreateRequest .setBusinessObjectDefinitionName(businessObjectFormatKey.getBusinessObjectDefinitionName()); businessObjectFormatCreateRequest .setBusinessObjectFormatUsage(businessObjectFormatKey.getBusinessObjectFormatUsage()); businessObjectFormatCreateRequest .setBusinessObjectFormatFileType(businessObjectFormatKey.getBusinessObjectFormatFileType()); } /** * Creates and persists a new business object format entity from the request information. * * @param request the request. * @param businessObjectFormatEntityParents parent business object format entity * * @return the newly created business object format entity. */ private BusinessObjectFormatEntity createBusinessObjectFormatEntity(BusinessObjectFormatCreateRequest request, BusinessObjectDefinitionEntity businessObjectDefinitionEntity, FileTypeEntity fileTypeEntity, Integer businessObjectFormatVersion, List<BusinessObjectFormatEntity> businessObjectFormatEntityParents) { BusinessObjectFormatEntity businessObjectFormatEntity = new BusinessObjectFormatEntity(); businessObjectFormatEntity.setBusinessObjectDefinition(businessObjectDefinitionEntity); businessObjectFormatEntity.setUsage(request.getBusinessObjectFormatUsage()); businessObjectFormatEntity.setFileType(fileTypeEntity); businessObjectFormatEntity.setBusinessObjectFormatVersion(businessObjectFormatVersion); businessObjectFormatEntity.setLatestVersion(Boolean.TRUE); businessObjectFormatEntity.setPartitionKey(request.getPartitionKey()); businessObjectFormatEntity.setDescription(request.getDescription()); businessObjectFormatEntity.setDocumentSchema(getTrimmedString(request.getDocumentSchema())); businessObjectFormatEntity.setDocumentSchemaUrl(getTrimmedString(request.getDocumentSchemaUrl())); // Create the attributes if they are specified. if (!CollectionUtils.isEmpty(request.getAttributes())) { List<BusinessObjectFormatAttributeEntity> attributeEntities = new ArrayList<>(); businessObjectFormatEntity.setAttributes(attributeEntities); for (Attribute attribute : request.getAttributes()) { BusinessObjectFormatAttributeEntity attributeEntity = new BusinessObjectFormatAttributeEntity(); attributeEntities.add(attributeEntity); attributeEntity.setBusinessObjectFormat(businessObjectFormatEntity); attributeEntity.setName(attribute.getName()); attributeEntity.setValue(attribute.getValue()); } } businessObjectFormatEntity.setAttributeDefinitions( createAttributeDefinitionEntities(request.getAttributeDefinitions(), businessObjectFormatEntity)); // Add optional schema information. populateBusinessObjectFormatSchema(businessObjectFormatEntity, request.getSchema()); //set the parents businessObjectFormatEntity.setBusinessObjectFormatParents(businessObjectFormatEntityParents); // Persist and return the new entity. return businessObjectFormatDao.saveAndRefresh(businessObjectFormatEntity); } /** * Creates a list of attribute definition entities. * * @param attributeDefinitions the list of attribute definitions * @param businessObjectFormatEntity the business object format entity * * @return the newly created list of attribute definition entities */ private List<BusinessObjectDataAttributeDefinitionEntity> createAttributeDefinitionEntities( List<AttributeDefinition> attributeDefinitions, BusinessObjectFormatEntity businessObjectFormatEntity) { List<BusinessObjectDataAttributeDefinitionEntity> attributeDefinitionEntities = new ArrayList<>(); if (!CollectionUtils.isEmpty(attributeDefinitions)) { for (AttributeDefinition attributeDefinition : attributeDefinitions) { // Create a new business object data attribute definition entity. BusinessObjectDataAttributeDefinitionEntity attributeDefinitionEntity = new BusinessObjectDataAttributeDefinitionEntity(); attributeDefinitionEntities.add(attributeDefinitionEntity); attributeDefinitionEntity.setBusinessObjectFormat(businessObjectFormatEntity); attributeDefinitionEntity.setName(attributeDefinition.getName()); // For the "publish" option, default a Boolean null value to "false". attributeDefinitionEntity.setPublish(BooleanUtils.isTrue(attributeDefinition.isPublish())); } } return attributeDefinitionEntities; } /** * Adds business object schema information to the business object format entity. * * @param businessObjectFormatEntity the business object format entity * @param schema the schema from the business object format create request */ private void populateBusinessObjectFormatSchema(BusinessObjectFormatEntity businessObjectFormatEntity, Schema schema) { // Add optional schema information. if (schema != null) { // If optional partition key group value is specified, get the partition key group entity and ensure it exists. PartitionKeyGroupEntity partitionKeyGroupEntity = null; if (StringUtils.isNotBlank(schema.getPartitionKeyGroup())) { partitionKeyGroupEntity = partitionKeyGroupDaoHelper .getPartitionKeyGroupEntity(schema.getPartitionKeyGroup()); } businessObjectFormatEntity.setNullValue(schema.getNullValue()); businessObjectFormatEntity.setDelimiter(schema.getDelimiter()); businessObjectFormatEntity.setCollectionItemsDelimiter(schema.getCollectionItemsDelimiter()); businessObjectFormatEntity.setMapKeysDelimiter(schema.getMapKeysDelimiter()); businessObjectFormatEntity.setEscapeCharacter(schema.getEscapeCharacter()); businessObjectFormatEntity.setPartitionKeyGroup(partitionKeyGroupEntity); // Create a schema column entities collection, if needed. Collection<SchemaColumnEntity> schemaColumnEntities = businessObjectFormatEntity.getSchemaColumns(); if (schemaColumnEntities == null) { schemaColumnEntities = new ArrayList<>(); businessObjectFormatEntity.setSchemaColumns(schemaColumnEntities); } // Create a map that will easily let us keep track of schema column entities we're creating by their column name. Map<String, SchemaColumnEntity> schemaColumnMap = new HashMap<>(); // Create the schema columns for both the data columns and then the partition columns. // Since both lists share the same schema column entity list, we will use a common method to process them in the same way. createSchemaColumnEntities(schema.getColumns(), false, schemaColumnEntities, schemaColumnMap, businessObjectFormatEntity); createSchemaColumnEntities(schema.getPartitions(), true, schemaColumnEntities, schemaColumnMap, businessObjectFormatEntity); } } /** * Creates the schema column entities. * * @param schemaColumns the list of schema columns (for either the data columns or the partition columns). * @param isPartitionList A flag to specify whether the list of schema columns is a data column or a partition column list. * @param schemaColumnEntityList the list of schema column entities we're creating. This method will add a new one to the list if it hasn't been created * before. * @param schemaColumnEntityMap a map of schema column entity names to the schema column entity. This is used so we don't have to keep searching the * schemaColumnEntityList which is less efficient. * @param businessObjectFormatEntity the business object format entity to associated each newly created schema column entity with. */ private void createSchemaColumnEntities(List<SchemaColumn> schemaColumns, boolean isPartitionList, Collection<SchemaColumnEntity> schemaColumnEntityList, Map<String, SchemaColumnEntity> schemaColumnEntityMap, BusinessObjectFormatEntity businessObjectFormatEntity) { // Create the relative database entities if schema columns are specified. if (!CollectionUtils.isEmpty(schemaColumns)) { // Initialize a position counter since the order we encounter the schema columns is the order we will set on the entity. int position = 1; // Loop through each schema column in the list. for (SchemaColumn schemaColumn : schemaColumns) { // See if we created the schema column already. If not, create it and add it to the map and the collection. SchemaColumnEntity schemaColumnEntity = schemaColumnEntityMap.get(schemaColumn.getName()); if (schemaColumnEntity == null) { schemaColumnEntity = createSchemaColumnEntity(schemaColumn, businessObjectFormatEntity); schemaColumnEntityList.add(schemaColumnEntity); schemaColumnEntityMap.put(schemaColumn.getName(), schemaColumnEntity); } // Set the position or partition level depending on the type of object we're processing. if (isPartitionList) { schemaColumnEntity.setPartitionLevel(position++); } else { schemaColumnEntity.setPosition(position++); } } } } /** * Creates a new schema column entity from the schema column. * * @param schemaColumn the schema column. * @param businessObjectFormatEntity the business object format entity to associated each newly created schema column entity with. * * @return the newly created schema column entity. */ private SchemaColumnEntity createSchemaColumnEntity(SchemaColumn schemaColumn, BusinessObjectFormatEntity businessObjectFormatEntity) { SchemaColumnEntity schemaColumnEntity = new SchemaColumnEntity(); schemaColumnEntity.setBusinessObjectFormat(businessObjectFormatEntity); schemaColumnEntity.setName(schemaColumn.getName()); schemaColumnEntity.setType(schemaColumn.getType()); schemaColumnEntity.setSize(schemaColumn.getSize()); schemaColumnEntity.setRequired(schemaColumn.isRequired()); schemaColumnEntity.setDefaultValue(schemaColumn.getDefaultValue()); schemaColumnEntity.setDescription(schemaColumn.getDescription()); return schemaColumnEntity; } /** * Deletes business object format schema information for the specified business object format entity. * * @param businessObjectFormatEntity the business object format entity */ private void clearBusinessObjectFormatSchema(BusinessObjectFormatEntity businessObjectFormatEntity) { businessObjectFormatEntity.setNullValue(null); businessObjectFormatEntity.setDelimiter(null); businessObjectFormatEntity.setCollectionItemsDelimiter(null); businessObjectFormatEntity.setMapKeysDelimiter(null); businessObjectFormatEntity.setEscapeCharacter(null); businessObjectFormatEntity.setPartitionKeyGroup(null); businessObjectFormatEntity.getSchemaColumns().clear(); } /** * Updates business object format attributes * * @param businessObjectFormatEntity the business object format entity * @param attributes the attributes */ private void updateBusinessObjectFormatAttributesHelper(BusinessObjectFormatEntity businessObjectFormatEntity, List<Attribute> attributes) { // Update the attributes. // Load all existing attribute entities in a map with a "lowercase" attribute name as the key for case insensitivity. Map<String, BusinessObjectFormatAttributeEntity> existingAttributeEntities = new HashMap<>(); for (BusinessObjectFormatAttributeEntity attributeEntity : businessObjectFormatEntity.getAttributes()) { String mapKey = attributeEntity.getName().toLowerCase(); if (existingAttributeEntities.containsKey(mapKey)) { throw new IllegalStateException( String.format("Found duplicate attribute with name \"%s\" for business object format {%s}.", mapKey, businessObjectFormatHelper .businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity))); } existingAttributeEntities.put(mapKey, attributeEntity); } // Process the list of attributes to determine that business object definition attribute entities should be created, updated, or deleted. List<BusinessObjectFormatAttributeEntity> createdAttributeEntities = new ArrayList<>(); List<BusinessObjectFormatAttributeEntity> retainedAttributeEntities = new ArrayList<>(); if (!CollectionUtils.isEmpty(attributes)) { for (Attribute attribute : attributes) { // Use a "lowercase" attribute name for case insensitivity. String lowercaseAttributeName = attribute.getName().toLowerCase(); if (existingAttributeEntities.containsKey(lowercaseAttributeName)) { // Check if the attribute value needs to be updated. BusinessObjectFormatAttributeEntity attributeEntity = existingAttributeEntities .get(lowercaseAttributeName); if (!StringUtils.equals(attribute.getValue(), attributeEntity.getValue())) { // Update the business object attribute entity. attributeEntity.setValue(attribute.getValue()); } // Add this entity to the list of business object definition attribute entities to be retained. retainedAttributeEntities.add(attributeEntity); } else { // Create a new business object attribute entity. BusinessObjectFormatAttributeEntity attributeEntity = new BusinessObjectFormatAttributeEntity(); businessObjectFormatEntity.getAttributes().add(attributeEntity); attributeEntity.setBusinessObjectFormat(businessObjectFormatEntity); attributeEntity.setName(attribute.getName()); attributeEntity.setValue(attribute.getValue()); // Add this entity to the list of the newly created business object definition attribute entities. createdAttributeEntities.add(attributeEntity); } } } // Remove any of the currently existing attribute entities that did not get onto the retained entities list. businessObjectFormatEntity.getAttributes().retainAll(retainedAttributeEntities); // Add all of the newly created business object definition attribute entities. businessObjectFormatEntity.getAttributes().addAll(createdAttributeEntities); } /** * Updates business object format attribute definitions * * @param businessObjectFormatEntity the business object format entity * @param attributeDefinitions the attributes */ private void updateBusinessObjectFormatAttributeDefinitionsHelper( BusinessObjectFormatEntity businessObjectFormatEntity, List<AttributeDefinition> attributeDefinitions) { // Update the attribute definitions. // Load all existing attribute definition entities in a map with a "lowercase" attribute definition name as the key for case insensitivity. Map<String, BusinessObjectDataAttributeDefinitionEntity> existingAttributeDefinitionEntities = businessObjectFormatEntity .getAttributeDefinitions().stream() .collect(Collectors.toMap(attributeDefinition -> attributeDefinition.getName().toLowerCase(), attributeDefinition -> attributeDefinition)); // Process the list of attribute definitions to determine that business object definition attribute entities should be created, updated, or deleted. List<BusinessObjectDataAttributeDefinitionEntity> createdAttributeDefinitionEntities = new ArrayList<>(); List<BusinessObjectDataAttributeDefinitionEntity> retainedAttributeDefinitionEntities = new ArrayList<>(); for (AttributeDefinition attributeDefinition : attributeDefinitions) { // Use a "lowercase" attribute name for case insensitivity. String lowercaseAttributeName = attributeDefinition.getName().toLowerCase(); if (existingAttributeDefinitionEntities.containsKey(lowercaseAttributeName)) { // Check if the attribute definition value needs to be updated. BusinessObjectDataAttributeDefinitionEntity businessObjectDataAttributeDefinitionEntity = existingAttributeDefinitionEntities .get(lowercaseAttributeName); if (!attributeDefinition.isPublish() .equals(businessObjectDataAttributeDefinitionEntity.getPublish())) { // Update the business object attribute entity. businessObjectDataAttributeDefinitionEntity.setPublish(attributeDefinition.isPublish()); } // Add this entity to the list of business object definition attribute entities to be retained. retainedAttributeDefinitionEntities.add(businessObjectDataAttributeDefinitionEntity); } else { // Create a new business object attribute entity. BusinessObjectDataAttributeDefinitionEntity businessObjectDataAttributeDefinitionEntity = new BusinessObjectDataAttributeDefinitionEntity(); businessObjectFormatEntity.getAttributeDefinitions() .add(businessObjectDataAttributeDefinitionEntity); businessObjectDataAttributeDefinitionEntity.setBusinessObjectFormat(businessObjectFormatEntity); businessObjectDataAttributeDefinitionEntity.setName(attributeDefinition.getName()); businessObjectDataAttributeDefinitionEntity .setPublish(BooleanUtils.isTrue(attributeDefinition.isPublish())); // Add this entity to the list of the newly created business object definition attribute entities. createdAttributeDefinitionEntities.add(businessObjectDataAttributeDefinitionEntity); } } // Remove any of the currently existing attribute entities that did not get onto the retained entities list. businessObjectFormatEntity.getAttributeDefinitions().retainAll(retainedAttributeDefinitionEntities); // Add all of the newly created business object definition attribute entities. businessObjectFormatEntity.getAttributeDefinitions().addAll(createdAttributeDefinitionEntities); } /** * Validates business object format attribute definitions * * @param attributeDefinitions the attribute definitions */ private void validateAndTrimBusinessObjectFormatAttributeDefinitionsHelper( List<AttributeDefinition> attributeDefinitions) { Assert.notNull(attributeDefinitions, "A business object format attribute definitions list is required."); Map<String, AttributeDefinition> attributeDefinitionNameValidationMap = new HashMap<>(); for (AttributeDefinition attributeDefinition : attributeDefinitions) { Assert.hasText(attributeDefinition.getName(), "An attribute definition name must be specified."); attributeDefinition.setName(attributeDefinition.getName().trim()); // Ensure the attribute defination key isn't a duplicate by using a map with a "lowercase" name as the key for case insensitivity. String lowercaseAttributeDefinitionName = attributeDefinition.getName().toLowerCase(); if (attributeDefinitionNameValidationMap.containsKey(lowercaseAttributeDefinitionName)) { throw new IllegalArgumentException(String.format( "Duplicate attribute definition name \"%s\" found.", attributeDefinition.getName())); } attributeDefinitionNameValidationMap.put(lowercaseAttributeDefinitionName, attributeDefinition); } } /** * Removes the leading and trailing white spaces in the given input String * * @param inputString - inputString * * @return inputString with leading and trailing white spaces removed. */ private String getTrimmedString(String inputString) { return inputString != null ? inputString.trim() : inputString; } }