org.finra.herd.service.helper.BusinessObjectDataDaoHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of herd-service Show documentation
Show all versions of herd-service Show documentation
This project contains the business service code. This is a classic service tier where business logic is defined along with it's associated
transaction management configuration.
/*
* 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.helper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.stream.Collectors;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.finra.herd.core.helper.ConfigurationHelper;
import org.finra.herd.dao.BusinessObjectDataDao;
import org.finra.herd.dao.ExpectedPartitionValueDao;
import org.finra.herd.dao.StorageFileDao;
import org.finra.herd.dao.StorageUnitDao;
import org.finra.herd.model.AlreadyExistsException;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.api.xml.Attribute;
import org.finra.herd.model.api.xml.BusinessObjectData;
import org.finra.herd.model.api.xml.BusinessObjectDataCreateRequest;
import org.finra.herd.model.api.xml.BusinessObjectDataInvalidateUnregisteredResponse;
import org.finra.herd.model.api.xml.BusinessObjectDataKey;
import org.finra.herd.model.api.xml.BusinessObjectFormat;
import org.finra.herd.model.api.xml.BusinessObjectFormatKey;
import org.finra.herd.model.api.xml.PartitionValueFilter;
import org.finra.herd.model.api.xml.PartitionValueRange;
import org.finra.herd.model.api.xml.StorageDirectory;
import org.finra.herd.model.api.xml.StorageFile;
import org.finra.herd.model.api.xml.StorageUnit;
import org.finra.herd.model.api.xml.StorageUnitCreateRequest;
import org.finra.herd.model.dto.ConfigurationValue;
import org.finra.herd.model.dto.S3FileTransferRequestParamsDto;
import org.finra.herd.model.jpa.BusinessObjectDataAttributeEntity;
import org.finra.herd.model.jpa.BusinessObjectDataEntity;
import org.finra.herd.model.jpa.BusinessObjectDataStatusEntity;
import org.finra.herd.model.jpa.BusinessObjectDataStatusHistoryEntity;
import org.finra.herd.model.jpa.BusinessObjectFormatEntity;
import org.finra.herd.model.jpa.ExpectedPartitionValueEntity;
import org.finra.herd.model.jpa.NotificationEventTypeEntity;
import org.finra.herd.model.jpa.StorageEntity;
import org.finra.herd.model.jpa.StorageFileEntity;
import org.finra.herd.model.jpa.StoragePlatformEntity;
import org.finra.herd.model.jpa.StorageUnitEntity;
import org.finra.herd.model.jpa.StorageUnitStatusEntity;
import org.finra.herd.service.BusinessObjectDataService;
import org.finra.herd.service.MessageNotificationEventService;
import org.finra.herd.service.NotificationEventService;
import org.finra.herd.service.S3Service;
/**
* Helper for business object data related operations which require DAO.
*/
@Component
public class BusinessObjectDataDaoHelper
{
private static final List NULL_VALUE_LIST = Arrays.asList((String) null);
@Autowired
private AlternateKeyHelper alternateKeyHelper;
@Autowired
private AttributeDaoHelper attributeDaoHelper;
@Autowired
private AttributeHelper attributeHelper;
@Autowired
private BusinessObjectDataDao businessObjectDataDao;
@Autowired
private BusinessObjectDataHelper businessObjectDataHelper;
@Autowired
private BusinessObjectDataStatusDaoHelper businessObjectDataStatusDaoHelper;
@Autowired
private BusinessObjectFormatDaoHelper businessObjectFormatDaoHelper;
@Autowired
private BusinessObjectFormatHelper businessObjectFormatHelper;
@Autowired
private ConfigurationHelper configurationHelper;
@Autowired
private ExpectedPartitionValueDao expectedPartitionValueDao;
@Autowired
private MessageNotificationEventService messageNotificationEventService;
@Autowired
private NotificationEventService notificationEventService;
@Autowired
private S3KeyPrefixHelper s3KeyPrefixHelper;
@Autowired
private S3Service s3Service;
@Autowired
private StorageDaoHelper storageDaoHelper;
@Autowired
private StorageFileDao storageFileDao;
@Autowired
private StorageFileHelper storageFileHelper;
@Autowired
private StorageHelper storageHelper;
@Autowired
private StorageUnitDao storageUnitDao;
@Autowired
private StorageUnitDaoHelper storageUnitDaoHelper;
@Autowired
private StorageUnitStatusDaoHelper storageUnitStatusDaoHelper;
/**
* Build partition filters based on the specified partition value filters. This method also validates the partition value filters (including partition
* keys) against the business object format schema. When request contains multiple partition value filters, the system will check business object data
* availability for n-fold Cartesian product of the partition values specified, where n is a number of partition value filters (partition value sets).
*
* @param partitionValueFilters the list of partition value filters
* @param standalonePartitionValueFilter the standalone partition value filter
* @param businessObjectFormatKey the business object format key
* @param businessObjectDataVersion the business object data version
* @param storageEntities the optional list of storage entities
* @param storagePlatformEntity the optional storage platform entity, e.g. S3 for Hive DDL. It is ignored when the list of storage entities is not empty
* @param excludedStoragePlatformEntity the optional storage platform entity to be excluded from search. It is ignored when the list of storage entities is
* not empty or the storage platform entity is specified
* @param businessObjectFormatEntity the business object format entity
*
* @return the list of partition filters
*/
public List> buildPartitionFilters(List partitionValueFilters, PartitionValueFilter standalonePartitionValueFilter,
BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion, List storageEntities,
StoragePlatformEntity storagePlatformEntity, StoragePlatformEntity excludedStoragePlatformEntity, BusinessObjectFormatEntity businessObjectFormatEntity)
{
// Build a list of partition value filters to process based on the specified partition value filters.
List partitionValueFiltersToProcess = getPartitionValuesToProcess(partitionValueFilters, standalonePartitionValueFilter);
// Build a map of column positions and the relative partition values.
Map> partitionValues = new HashMap<>();
// Initialize the map with null partition values for all possible primary and sub-partition values. Partition column position uses one-based numbering.
for (int i = 0; i < BusinessObjectDataEntity.MAX_SUBPARTITIONS + 1; i++)
{
partitionValues.put(i, NULL_VALUE_LIST);
}
// Process all partition value filters one by one and populate the relative entries in the map.
for (PartitionValueFilter partitionValueFilter : partitionValueFiltersToProcess)
{
// Get the partition key. If partition key is not specified, use the primary partition column.
String partitionKey = StringUtils.isNotBlank(partitionValueFilter.getPartitionKey()) ? partitionValueFilter.getPartitionKey() :
businessObjectFormatEntity.getPartitionKey();
// Get the partition column position (one-based numbering).
int partitionColumnPosition = getPartitionColumnPosition(partitionKey, businessObjectFormatEntity);
// Get unique and sorted list of partition values to check the availability for.
List uniqueAndSortedPartitionValues =
getPartitionValues(partitionValueFilter, partitionKey, partitionColumnPosition, businessObjectFormatKey, businessObjectDataVersion,
storageEntities, storagePlatformEntity, excludedStoragePlatformEntity, businessObjectFormatEntity);
// Add this partition value filter to the map.
List previousPartitionValues = partitionValues.put(partitionColumnPosition - 1, uniqueAndSortedPartitionValues);
// Check if this partition column has not been already added.
if (!NULL_VALUE_LIST.equals(previousPartitionValues))
{
throw new IllegalArgumentException("Partition value filters specify duplicate partition columns.");
}
}
// When request contains multiple partition value filters, the system will check business object data availability for n-fold Cartesian product
// of the partition values specified, where n is a number of partition value filters (partition value sets).
List crossProductResult = getCrossProduct(partitionValues);
List> partitionFilters = new ArrayList<>();
for (String[] crossProductRow : crossProductResult)
{
partitionFilters.add(Arrays.asList(crossProductRow));
}
return partitionFilters;
}
/**
* Creates a new business object data from the request information.
*
* @param request the request
*
* @return the newly created and persisted business object data
*/
public BusinessObjectData createBusinessObjectData(BusinessObjectDataCreateRequest request)
{
// By default, fileSize value is required.
return createBusinessObjectData(request, true);
}
/**
* Creates a new business object data from the request information.
*
* @param request the request
* @param fileSizeRequired specifies if fileSizeBytes value is required or not
*
* @return the newly created and persisted business object data
*/
public BusinessObjectData createBusinessObjectData(BusinessObjectDataCreateRequest request, boolean fileSizeRequired)
{
if (StringUtils.isBlank(request.getStatus()))
{
request.setStatus(BusinessObjectDataStatusEntity.VALID);
}
else
{
request.setStatus(request.getStatus().trim());
}
// Get the status entity if status is specified else set it to VALID
BusinessObjectDataStatusEntity businessObjectDataStatusEntity =
businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(request.getStatus());
// Perform the validation.
validateBusinessObjectDataCreateRequest(request, fileSizeRequired, businessObjectDataStatusEntity);
// Get the business object format for the specified parameters and make sure it exists.
BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper.getBusinessObjectFormatEntity(
new BusinessObjectFormatKey(request.getNamespace(), request.getBusinessObjectDefinitionName(), request.getBusinessObjectFormatUsage(),
request.getBusinessObjectFormatFileType(), request.getBusinessObjectFormatVersion()));
attributeDaoHelper
.validateAttributesAgainstBusinessObjectDataAttributeDefinitions(request.getAttributes(), businessObjectFormatEntity.getAttributeDefinitions());
// Ensure the specified partition key matches what's configured within the business object format.
Assert.isTrue(businessObjectFormatEntity.getPartitionKey().equalsIgnoreCase(request.getPartitionKey()), String
.format("Partition key \"%s\" doesn't match configured business object format partition key \"%s\".", request.getPartitionKey(),
businessObjectFormatEntity.getPartitionKey()));
// Get the latest format version for this business object data, if it exists.
BusinessObjectDataEntity existingBusinessObjectDataEntity = businessObjectDataDao.getBusinessObjectDataByAltKey(
new BusinessObjectDataKey(request.getNamespace(), request.getBusinessObjectDefinitionName(), request.getBusinessObjectFormatUsage(),
request.getBusinessObjectFormatFileType(), request.getBusinessObjectFormatVersion(), request.getPartitionValue(),
request.getSubPartitionValues(), null));
if (existingBusinessObjectDataEntity != null)
{
if (!Boolean.TRUE.equals(request.isCreateNewVersion()))
{
throw new AlreadyExistsException("Unable to create business object data because it already exists and a new version is not allowed " +
"since the \"createNewVersion\" flag is not set to \"true\" in the request.");
}
else if (Boolean.TRUE.equals(existingBusinessObjectDataEntity.getStatus().getPreRegistrationStatus()))
{
throw new AlreadyExistsException(String.format(
"Unable to create business object data because it already exists and a new version is not allowed since the latest version is" +
" still in \"%s\", which is one of the pre-registration statuses.", existingBusinessObjectDataEntity.getStatus().getCode()));
}
}
// Create a business object data entity from the request information.
// Please note that simply adding 1 to the latest version without "DB locking" is sufficient here,
// even for multi-threading, since we are relying on the DB having version as part of the alternate key.
Integer businessObjectDataVersion = existingBusinessObjectDataEntity == null ? BusinessObjectDataEntity.BUSINESS_OBJECT_DATA_INITIAL_VERSION :
existingBusinessObjectDataEntity.getVersion() + 1;
BusinessObjectDataEntity newVersionBusinessObjectDataEntity =
createBusinessObjectDataEntity(request, businessObjectFormatEntity, businessObjectDataVersion, businessObjectDataStatusEntity);
// Update the existing latest business object data version entity, so it would not be flagged as the latest version anymore.
if (existingBusinessObjectDataEntity != null)
{
existingBusinessObjectDataEntity.setLatestVersion(Boolean.FALSE);
businessObjectDataDao.saveAndRefresh(existingBusinessObjectDataEntity);
}
// Add an entry to the business object data status history table.
BusinessObjectDataStatusHistoryEntity businessObjectDataStatusHistoryEntity = new BusinessObjectDataStatusHistoryEntity();
businessObjectDataStatusHistoryEntity.setBusinessObjectData(newVersionBusinessObjectDataEntity);
businessObjectDataStatusHistoryEntity.setStatus(businessObjectDataStatusEntity);
List businessObjectDataStatusHistoryEntities = new ArrayList<>();
businessObjectDataStatusHistoryEntities.add(businessObjectDataStatusHistoryEntity);
newVersionBusinessObjectDataEntity.setHistoricalStatuses(businessObjectDataStatusHistoryEntities);
// Persist the new entity.
newVersionBusinessObjectDataEntity = businessObjectDataDao.saveAndRefresh(newVersionBusinessObjectDataEntity);
// Create a status change notification to be sent on create business object data event.
messageNotificationEventService
.processBusinessObjectDataStatusChangeNotificationEvent(businessObjectDataHelper.getBusinessObjectDataKey(newVersionBusinessObjectDataEntity),
businessObjectDataStatusEntity.getCode(), null);
// Create and return the business object data object from the persisted entity.
return businessObjectDataHelper.createBusinessObjectDataFromEntity(newVersionBusinessObjectDataEntity);
}
/**
* Creates a storage unit entity per specified parameters.
*
* @param businessObjectDataEntity the business object data entity
* @param storageEntity the storage entity
* @param storageDirectory the storage directory
* @param storageFiles the list of storage files
* @param isDiscoverStorageFiles specifies if
*
* @return the newly created storage unit entity
*/
public StorageUnitEntity createStorageUnitEntity(BusinessObjectDataEntity businessObjectDataEntity, StorageEntity storageEntity,
StorageDirectory storageDirectory, List storageFiles, Boolean isDiscoverStorageFiles)
{
// Get the storage unit status entity for the ENABLED status.
StorageUnitStatusEntity storageUnitStatusEntity = storageUnitStatusDaoHelper.getStorageUnitStatusEntity(StorageUnitStatusEntity.ENABLED);
// Set up flags which are used to make flow logic easier.
boolean isS3StoragePlatform = storageEntity.getStoragePlatform().getName().equals(StoragePlatformEntity.S3);
boolean isStorageDirectorySpecified = (storageDirectory != null);
boolean validatePathPrefix = storageHelper
.getBooleanStorageAttributeValueByName(configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_VALIDATE_PATH_PREFIX), storageEntity,
false, true);
boolean validateFileExistence = storageHelper
.getBooleanStorageAttributeValueByName(configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_VALIDATE_FILE_EXISTENCE), storageEntity,
false, true);
boolean validateFileSize = storageHelper
.getBooleanStorageAttributeValueByName(configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_VALIDATE_FILE_SIZE), storageEntity,
false, true);
// Ensure that file size validation is not enabled without file existence validation.
if (validateFileSize)
{
Assert.isTrue(validateFileExistence,
String.format("Storage \"%s\" has file size validation enabled without file existence validation.", storageEntity.getName()));
}
String expectedS3KeyPrefix = null;
// Retrieve S3 key prefix velocity template storage attribute value and store it in memory.
// Please note that it is not required, so we pass in a "false" flag.
String s3KeyPrefixVelocityTemplate = storageHelper
.getStorageAttributeValueByName(configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_KEY_PREFIX_VELOCITY_TEMPLATE), storageEntity,
false);
if (StringUtils.isNotBlank(s3KeyPrefixVelocityTemplate))
{
// If the storage has any validation configured, get the expected S3 key prefix.
expectedS3KeyPrefix = s3KeyPrefixHelper.buildS3KeyPrefix(s3KeyPrefixVelocityTemplate, businessObjectDataEntity.getBusinessObjectFormat(),
businessObjectDataHelper.getBusinessObjectDataKey(businessObjectDataEntity), storageEntity.getName());
}
if ((validatePathPrefix || validateFileExistence) && isS3StoragePlatform)
{
// If path prefix validation is enabled, validate that S3 key prefix velocity template is configured.
Assert.isTrue(!validatePathPrefix || StringUtils.isNotBlank(s3KeyPrefixVelocityTemplate),
String.format("Storage \"%s\" has enabled path validation without S3 key prefix velocity template configured.", storageEntity.getName()));
}
// Process storage directory path if it is specified.
String directoryPath = null;
if (isStorageDirectorySpecified)
{
// Get the specified directory path.
directoryPath = storageDirectory.getDirectoryPath();
// If the validate path prefix flag is configured for this storage, validate the directory path value.
if (validatePathPrefix && isS3StoragePlatform)
{
// Ensure the directory path adheres to the S3 naming convention.
Assert.isTrue(directoryPath.equals(expectedS3KeyPrefix),
String.format("Specified directory path \"%s\" does not match the expected S3 key prefix \"%s\".", directoryPath, expectedS3KeyPrefix));
// Ensure that the directory path is not already registered with another business object data instance.
StorageUnitEntity alreadyRegisteredStorageUnitEntity = storageUnitDao.getStorageUnitByStorageAndDirectoryPath(storageEntity, directoryPath);
if (alreadyRegisteredStorageUnitEntity != null)
{
throw new AlreadyExistsException(String
.format("Storage directory \"%s\" in \"%s\" storage is already registered by the business object data {%s}.", directoryPath,
storageEntity.getName(),
businessObjectDataHelper.businessObjectDataEntityAltKeyToString(alreadyRegisteredStorageUnitEntity.getBusinessObjectData())));
}
}
}
else if (Boolean.TRUE.equals(businessObjectDataEntity.getStatus().getPreRegistrationStatus()))
{
directoryPath = expectedS3KeyPrefix;
}
// Create a storage unit entity.
StorageUnitEntity storageUnitEntity = new StorageUnitEntity();
storageUnitEntity.setStorage(storageEntity);
storageUnitEntity.setBusinessObjectData(businessObjectDataEntity);
storageUnitDaoHelper.setStorageUnitStatus(storageUnitEntity, storageUnitStatusEntity);
storageUnitEntity.setDirectoryPath(directoryPath);
// Discover storage files if storage file discovery is enabled. Otherwise, get the storage files specified in the request, if any.
List resultStorageFiles = BooleanUtils.isTrue(isDiscoverStorageFiles) ? discoverStorageFiles(storageEntity, directoryPath) : storageFiles;
// Create the storage file entities.
createStorageFileEntitiesFromStorageFiles(resultStorageFiles, storageEntity, BooleanUtils.isTrue(isDiscoverStorageFiles), expectedS3KeyPrefix,
storageUnitEntity, directoryPath, validatePathPrefix, validateFileExistence, validateFileSize, isS3StoragePlatform);
return storageUnitEntity;
}
/**
* Gets business object data based on the key information.
*
* @param businessObjectDataKey the business object data key.
*
* @return the business object data.
*/
public BusinessObjectDataEntity getBusinessObjectDataEntity(BusinessObjectDataKey businessObjectDataKey)
{
// Retrieve the business object data entity regardless of its status.
return getBusinessObjectDataEntityByKeyAndStatus(businessObjectDataKey, null);
}
/**
* Retrieves business object data by it's key. If a format version isn't specified, the latest available format version (for this partition value) will be
* used. If a business object data version isn't specified, the latest data version based on the specified business object data status is returned. When
* both business object data version and business object data status are not specified, the latest data version for each set of partition values will be
* used regardless of the status.
*
* @param businessObjectDataKey the business object data key
* @param businessObjectDataStatusEntity the optional business object data status entity. This parameter is ignored when the business object data version is
* specified
*
* @return the business object data
*/
public BusinessObjectDataEntity getBusinessObjectDataEntityByKeyAndStatus(BusinessObjectDataKey businessObjectDataKey,
BusinessObjectDataStatusEntity businessObjectDataStatusEntity)
{
// Get the business object data based on the specified parameters.
BusinessObjectDataEntity businessObjectDataEntity =
businessObjectDataDao.getBusinessObjectDataByAltKeyAndStatus(businessObjectDataKey, businessObjectDataStatusEntity);
// Make sure that business object data exists.
if (businessObjectDataEntity == null)
{
throw new ObjectNotFoundException(String.format(
"Business object data {namespace: \"%s\", businessObjectDefinitionName: \"%s\", businessObjectFormatUsage: \"%s\", " +
"businessObjectFormatFileType: \"%s\", businessObjectFormatVersion: %d, businessObjectDataPartitionValue: \"%s\", " +
"businessObjectDataSubPartitionValues: \"%s\", businessObjectDataVersion: %d, businessObjectDataStatus: \"%s\"} doesn't exist.",
businessObjectDataKey.getNamespace(), businessObjectDataKey.getBusinessObjectDefinitionName(),
businessObjectDataKey.getBusinessObjectFormatUsage(), businessObjectDataKey.getBusinessObjectFormatFileType(),
businessObjectDataKey.getBusinessObjectFormatVersion(), businessObjectDataKey.getPartitionValue(),
CollectionUtils.isEmpty(businessObjectDataKey.getSubPartitionValues()) ? "" :
StringUtils.join(businessObjectDataKey.getSubPartitionValues(), ","), businessObjectDataKey.getBusinessObjectDataVersion(),
businessObjectDataStatusEntity != null ? businessObjectDataStatusEntity.getCode() : "null"));
}
// Return the retrieved business object data entity.
return businessObjectDataEntity;
}
/**
* Builds a list of partition values from the partition value filter. The partition range takes precedence over the list of partition values in the filter.
* If a range is specified the list of values will come from the expected partition values table for values within the specified range. If the list is
* specified, duplicates will be removed. In both cases, the list will be ordered ascending.
*
* @param partitionValueFilter the partition value filter that was validated to have exactly one partition value filter option
* @param partitionKey the partition key
* @param partitionColumnPosition the partition column position (one-based numbering)
* @param businessObjectFormatKey the business object format key
* @param businessObjectDataVersion the business object data version
* @param storageEntities the optional list of storage entities
* @param storagePlatformEntity the optional storage platform entity, e.g. S3 for Hive DDL. It is ignored when the list of storage names is not empty
* @param excludedStoragePlatformEntity the optional storage platform entity to be excluded from search. It is ignored when the list of storage names is not
* empty or the storage platform entity is specified
* @param businessObjectFormatEntity the business object format entity
*
* @return the unique and sorted partition value list
*/
public List getPartitionValues(PartitionValueFilter partitionValueFilter, String partitionKey, int partitionColumnPosition,
BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion, List storageEntities,
StoragePlatformEntity storagePlatformEntity, StoragePlatformEntity excludedStoragePlatformEntity, BusinessObjectFormatEntity businessObjectFormatEntity)
{
List partitionValues = new ArrayList<>();
if (partitionValueFilter.getPartitionValueRange() != null)
{
// A "partition value range" filter option is specified.
partitionValues = processPartitionValueRangeFilterOption(partitionValueFilter.getPartitionValueRange(), businessObjectFormatEntity);
}
else if (partitionValueFilter.getPartitionValues() != null)
{
// A "partition value list" filter option is specified.
partitionValues =
processPartitionValueListFilterOption(partitionValueFilter.getPartitionValues(), partitionKey, partitionColumnPosition, businessObjectFormatKey,
businessObjectDataVersion, storageEntities, storagePlatformEntity, excludedStoragePlatformEntity);
}
else if (partitionValueFilter.getLatestBeforePartitionValue() != null)
{
// A "latest before partition value" filter option is specified.
// Get business object data status entity for the VALID status.
BusinessObjectDataStatusEntity validBusinessObjectDataStatusEntity =
businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(BusinessObjectDataStatusEntity.VALID);
// Retrieve the maximum partition value before (inclusive) the specified partition value.
// If a business object data version isn't specified, the latest VALID business object data version will be used.
String maxPartitionValue = businessObjectDataDao
.getBusinessObjectDataMaxPartitionValue(partitionColumnPosition, businessObjectFormatKey, businessObjectDataVersion,
validBusinessObjectDataStatusEntity, storageEntities, storagePlatformEntity, excludedStoragePlatformEntity,
partitionValueFilter.getLatestBeforePartitionValue().getPartitionValue(), null);
if (maxPartitionValue != null)
{
partitionValues.add(maxPartitionValue);
}
else
{
throw new ObjectNotFoundException(
getLatestPartitionValueNotFoundErrorMessage("before", partitionValueFilter.getLatestBeforePartitionValue().getPartitionValue(),
partitionKey, businessObjectFormatKey, businessObjectDataVersion, storageEntities));
}
}
else
{
// A "latest after partition value" filter option is specified.
// Get business object data status entity for the VALID status.
BusinessObjectDataStatusEntity validBusinessObjectDataStatusEntity =
businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(BusinessObjectDataStatusEntity.VALID);
// Retrieve the maximum partition value before (inclusive) the specified partition value.
// If a business object data version isn't specified, the latest VALID business object data version will be used.
String maxPartitionValue = businessObjectDataDao
.getBusinessObjectDataMaxPartitionValue(partitionColumnPosition, businessObjectFormatKey, businessObjectDataVersion,
validBusinessObjectDataStatusEntity, storageEntities, storagePlatformEntity, excludedStoragePlatformEntity, null,
partitionValueFilter.getLatestAfterPartitionValue().getPartitionValue());
if (maxPartitionValue != null)
{
partitionValues.add(maxPartitionValue);
}
else
{
throw new ObjectNotFoundException(
getLatestPartitionValueNotFoundErrorMessage("after", partitionValueFilter.getLatestAfterPartitionValue().getPartitionValue(), partitionKey,
businessObjectFormatKey, businessObjectDataVersion, storageEntities));
}
}
// If a max partition values limit has been set, check the limit and throw an exception if the limit has been exceeded.
Integer availabilityDdlMaxPartitionValues = configurationHelper.getProperty(ConfigurationValue.AVAILABILITY_DDL_MAX_PARTITION_VALUES, Integer.class);
if ((availabilityDdlMaxPartitionValues != null) && (partitionValues.size() > availabilityDdlMaxPartitionValues))
{
throw new IllegalArgumentException(
"The number of partition values (" + partitionValues.size() + ") exceeds the system limit of " + availabilityDdlMaxPartitionValues + ".");
}
// Return the partition values.
return partitionValues;
}
/**
* Trigger business object data and storage unit notification for business object data creation event.
*
* @param businessObjectData the business object data
*/
public void triggerNotificationsForCreateBusinessObjectData(BusinessObjectData businessObjectData)
{
BusinessObjectDataKey businessObjectDataKey = businessObjectDataHelper.getBusinessObjectDataKey(businessObjectData);
// Create business object data notifications.
for (NotificationEventTypeEntity.EventTypesBdata eventType : Arrays
.asList(NotificationEventTypeEntity.EventTypesBdata.BUS_OBJCT_DATA_RGSTN, NotificationEventTypeEntity.EventTypesBdata.BUS_OBJCT_DATA_STTS_CHG))
{
notificationEventService.processBusinessObjectDataNotificationEventAsync(eventType, businessObjectDataKey, businessObjectData.getStatus(), null);
}
// Create storage unit notifications.
for (StorageUnit storageUnit : businessObjectData.getStorageUnits())
{
notificationEventService
.processStorageUnitNotificationEventAsync(NotificationEventTypeEntity.EventTypesStorageUnit.STRGE_UNIT_STTS_CHG, businessObjectDataKey,
storageUnit.getStorage().getName(), storageUnit.getStorageUnitStatus(), null);
}
}
/**
* Trigger business object data and storage unit notification for unregistered business object data invalidation event.
*
* @param businessObjectDataInvalidateUnregisteredResponse the business object data invalidate unregistered response
*/
public void triggerNotificationsForInvalidateUnregisteredBusinessObjectData(
BusinessObjectDataInvalidateUnregisteredResponse businessObjectDataInvalidateUnregisteredResponse)
{
for (BusinessObjectData businessObjectData : businessObjectDataInvalidateUnregisteredResponse.getRegisteredBusinessObjectDataList())
{
triggerNotificationsForCreateBusinessObjectData(businessObjectData);
}
}
/**
* Update the business object data status.
*
* @param businessObjectDataEntity the business object data entity
* @param status the status
*/
public void updateBusinessObjectDataStatus(BusinessObjectDataEntity businessObjectDataEntity, String status)
{
// Retrieve and ensure the status is valid.
BusinessObjectDataStatusEntity businessObjectDataStatusEntity = businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(status);
// Save the current status value.
String oldStatus = businessObjectDataEntity.getStatus().getCode();
// Update the entity with the new values.
businessObjectDataEntity.setStatus(businessObjectDataStatusEntity);
// Add an entry to the business object data status history table
BusinessObjectDataStatusHistoryEntity businessObjectDataStatusHistoryEntity = new BusinessObjectDataStatusHistoryEntity();
businessObjectDataEntity.getHistoricalStatuses().add(businessObjectDataStatusHistoryEntity);
businessObjectDataStatusHistoryEntity.setBusinessObjectData(businessObjectDataEntity);
businessObjectDataStatusHistoryEntity.setStatus(businessObjectDataStatusEntity);
// Persist the entity.
businessObjectDataDao.saveAndRefresh(businessObjectDataEntity);
// Sent a business object data status change notification.
messageNotificationEventService
.processBusinessObjectDataStatusChangeNotificationEvent(businessObjectDataHelper.getBusinessObjectDataKey(businessObjectDataEntity),
businessObjectDataStatusEntity.getCode(), oldStatus);
}
/**
* Returns a cloned version of the specified business object data key where all fields are made lowercase.
*
* @param businessObjectDataKey the business object data.
*
* @return the cloned business object data.
*/
private BusinessObjectDataKey cloneToLowerCase(BusinessObjectDataKey businessObjectDataKey)
{
BusinessObjectDataKey businessObjectDataKeyClone = new BusinessObjectDataKey();
businessObjectDataKeyClone.setNamespace(businessObjectDataKey.getNamespace().toLowerCase());
businessObjectDataKeyClone.setBusinessObjectDefinitionName(businessObjectDataKey.getBusinessObjectDefinitionName().toLowerCase());
businessObjectDataKeyClone.setBusinessObjectFormatUsage(businessObjectDataKey.getBusinessObjectFormatUsage().toLowerCase());
businessObjectDataKeyClone.setBusinessObjectFormatFileType(businessObjectDataKey.getBusinessObjectFormatFileType().toLowerCase());
businessObjectDataKeyClone.setBusinessObjectFormatVersion(businessObjectDataKey.getBusinessObjectFormatVersion());
businessObjectDataKeyClone.setPartitionValue(businessObjectDataKey.getPartitionValue());
businessObjectDataKeyClone.setBusinessObjectDataVersion(businessObjectDataKey.getBusinessObjectDataVersion());
businessObjectDataKeyClone.setSubPartitionValues(businessObjectDataKey.getSubPartitionValues());
return businessObjectDataKeyClone;
}
/**
* Creates a new business object data entity from the request information.
*
* @param request the request.
* @param businessObjectFormatEntity the business object format entity.
* @param businessObjectDataVersion the business object data version.
*
* @return the newly created business object data entity.
*/
private BusinessObjectDataEntity createBusinessObjectDataEntity(BusinessObjectDataCreateRequest request,
BusinessObjectFormatEntity businessObjectFormatEntity, Integer businessObjectDataVersion, BusinessObjectDataStatusEntity businessObjectDataStatusEntity)
{
// Create a new entity.
BusinessObjectDataEntity businessObjectDataEntity = new BusinessObjectDataEntity();
businessObjectDataEntity.setBusinessObjectFormat(businessObjectFormatEntity);
businessObjectDataEntity.setPartitionValue(request.getPartitionValue());
int subPartitionValuesCount = CollectionUtils.size(request.getSubPartitionValues());
businessObjectDataEntity.setPartitionValue2(subPartitionValuesCount > 0 ? request.getSubPartitionValues().get(0) : null);
businessObjectDataEntity.setPartitionValue3(subPartitionValuesCount > 1 ? request.getSubPartitionValues().get(1) : null);
businessObjectDataEntity.setPartitionValue4(subPartitionValuesCount > 2 ? request.getSubPartitionValues().get(2) : null);
businessObjectDataEntity.setPartitionValue5(subPartitionValuesCount > 3 ? request.getSubPartitionValues().get(3) : null);
businessObjectDataEntity.setVersion(businessObjectDataVersion);
businessObjectDataEntity.setLatestVersion(true);
businessObjectDataEntity.setStatus(businessObjectDataStatusEntity);
// Create the storage unit entities.
businessObjectDataEntity.setStorageUnits(createStorageUnitEntitiesFromStorageUnits(request.getStorageUnits(), businessObjectDataEntity));
// Create the attributes.
List attributeEntities = new ArrayList<>();
businessObjectDataEntity.setAttributes(attributeEntities);
if (CollectionUtils.isNotEmpty(request.getAttributes()))
{
for (Attribute attribute : request.getAttributes())
{
BusinessObjectDataAttributeEntity attributeEntity = new BusinessObjectDataAttributeEntity();
attributeEntities.add(attributeEntity);
attributeEntity.setBusinessObjectData(businessObjectDataEntity);
attributeEntity.setName(attribute.getName());
attributeEntity.setValue(attribute.getValue());
}
}
// Create the parents.
List businessObjectDataParents = new ArrayList<>();
businessObjectDataEntity.setBusinessObjectDataParents(businessObjectDataParents);
// Loop through all the business object data parents.
if (request.getBusinessObjectDataParents() != null)
{
for (BusinessObjectDataKey businessObjectDataKey : request.getBusinessObjectDataParents())
{
// Look up the business object data for each parent.
BusinessObjectDataEntity businessObjectDataParent = getBusinessObjectDataEntity(businessObjectDataKey);
// Add our newly created entity as a dependent (i.e. child) of the looked up parent.
businessObjectDataParent.getBusinessObjectDataChildren().add(businessObjectDataEntity);
// Add the looked up parent as a parent of our newly created entity.
businessObjectDataParents.add(businessObjectDataParent);
}
}
// Return the newly created entity.
return businessObjectDataEntity;
}
/**
* Creates a list of storage file entities from a list of storage files.
*
* @param storageFiles the list of storage files
* @param storageEntity the storage entity
* @param storageFilesDiscovered specifies whether the storage files were actually discovered in the storage
* @param expectedS3KeyPrefix the expected S3 key prefix
* @param storageUnitEntity the storage unit entity that storage file entities will belong to
* @param directoryPath the storage directory path
* @param validatePathPrefix specifies whether the storage has S3 key prefix validation enabled
* @param validateFileExistence specifies whether the storage has file existence enabled
* @param validateFileSize specifies whether the storage has file validation enabled
* @param isS3StoragePlatform specifies whether the storage platform type is S3
*
* @return the list of storage file entities
*/
private List createStorageFileEntitiesFromStorageFiles(List storageFiles, StorageEntity storageEntity,
boolean storageFilesDiscovered, String expectedS3KeyPrefix, StorageUnitEntity storageUnitEntity, String directoryPath, boolean validatePathPrefix,
boolean validateFileExistence, boolean validateFileSize, boolean isS3StoragePlatform)
{
List storageFileEntities = null;
// Process storage files if they are specified.
if (CollectionUtils.isNotEmpty(storageFiles))
{
storageFileEntities = new ArrayList<>();
storageUnitEntity.setStorageFiles(storageFileEntities);
// If the validate file existence flag is configured for this storage and storage files were not discovered, prepare for S3 file validation.
S3FileTransferRequestParamsDto params = null;
Map actualS3Keys = null;
if (validateFileExistence && isS3StoragePlatform && !storageFilesDiscovered)
{
// Get the validate file parameters.
params = getFileValidationParams(storageEntity, expectedS3KeyPrefix, storageUnitEntity, validatePathPrefix);
// When listing S3 files, we ignore 0 byte objects that represent S3 directories.
actualS3Keys = storageFileHelper.getStorageFilesMapFromS3ObjectSummaries(s3Service.listDirectory(params, true));
}
// If the validate path prefix flag is configured, ensure that there are no storage files already registered in this
// storage by some other business object data that start with the expected S3 key prefix.
if (validatePathPrefix && isS3StoragePlatform)
{
// Since the S3 key prefix represents a directory, we add a trailing '/' character to it.
String expectedS3KeyPrefixWithTrailingSlash = expectedS3KeyPrefix + "/";
Long registeredStorageFileCount = storageFileDao.getStorageFileCount(storageEntity.getName(), expectedS3KeyPrefixWithTrailingSlash);
if (registeredStorageFileCount > 0)
{
throw new AlreadyExistsException(String.format(
"Found %d storage file(s) matching \"%s\" S3 key prefix in \"%s\" " + "storage that is registered with another business object data.",
registeredStorageFileCount, expectedS3KeyPrefix, storageEntity.getName()));
}
}
for (StorageFile storageFile : storageFiles)
{
StorageFileEntity storageFileEntity = new StorageFileEntity();
storageFileEntities.add(storageFileEntity);
storageFileEntity.setStorageUnit(storageUnitEntity);
storageFileEntity.setPath(storageFile.getFilePath());
storageFileEntity.setFileSizeBytes(storageFile.getFileSizeBytes());
storageFileEntity.setRowCount(storageFile.getRowCount());
// Skip storage file validation if storage files were discovered.
if (!storageFilesDiscovered)
{
// Validate that the storage file path matches the key prefix if the validate path prefix flag is configured for this storage.
// Otherwise, if a directory path is specified, ensure it is consistent with the file path.
if (validatePathPrefix && isS3StoragePlatform)
{
// Ensure the S3 file key prefix adheres to the S3 naming convention.
Assert.isTrue(storageFileEntity.getPath().startsWith(expectedS3KeyPrefix), String
.format("Specified storage file path \"%s\" does not match the expected S3 key prefix \"%s\".", storageFileEntity.getPath(),
expectedS3KeyPrefix));
}
else if (directoryPath != null)
{
// When storage directory path is specified, ensure that storage file path starts with it.
Assert.isTrue(storageFileEntity.getPath().startsWith(directoryPath), String
.format("Storage file path \"%s\" does not match the storage directory path \"%s\".", storageFileEntity.getPath(), directoryPath));
}
// Ensure the file exists in S3 if the validate file existence flag is configured for this storage.
if (validateFileExistence && isS3StoragePlatform)
{
// Validate storage file.
storageFileHelper.validateStorageFile(storageFile, params.getS3BucketName(), actualS3Keys, validateFileSize);
}
}
}
}
return storageFileEntities;
}
/**
* Creates a list of storage unit entities from a list of storage unit create requests.
*
* @param storageUnitCreateRequests the storage unit create requests
* @param businessObjectDataEntity the business object data entity
*
* @return the list of storage unit entities.
*/
private List createStorageUnitEntitiesFromStorageUnits(List storageUnitCreateRequests,
BusinessObjectDataEntity businessObjectDataEntity)
{
// Create the storage units for the data.
List storageUnitEntities = new ArrayList<>();
for (StorageUnitCreateRequest storageUnit : storageUnitCreateRequests)
{
// Get the storage entity per request and verify that it exists.
StorageEntity storageEntity = storageDaoHelper.getStorageEntity(storageUnit.getStorageName());
// Create storage unit and add it to the result list.
storageUnitEntities.add(
createStorageUnitEntity(businessObjectDataEntity, storageEntity, storageUnit.getStorageDirectory(), storageUnit.getStorageFiles(),
storageUnit.isDiscoverStorageFiles()));
}
return storageUnitEntities;
}
/**
* Discovers storage files in the specified S3 storage that match the S3 key prefix.
*
* @param storageEntity the storage entity
* @param s3KeyPrefix the S3 key prefix
*
* @return the list of discovered storage files
*/
private List discoverStorageFiles(StorageEntity storageEntity, String s3KeyPrefix)
{
// Only S3 storage platform is currently supported for storage file discovery.
Assert.isTrue(storageEntity.getStoragePlatform().getName().equals(StoragePlatformEntity.S3),
String.format("Cannot discover storage files at \"%s\" storage platform.", storageEntity.getStoragePlatform().getName()));
// Get S3 bucket access parameters.
S3FileTransferRequestParamsDto params = storageHelper.getS3BucketAccessParams(storageEntity);
// Retrieve a list of all keys/objects from the S3 bucket matching the specified S3 key prefix.
// Since S3 key prefix represents the directory, we add a trailing '/' character to it, unless it is already present.
params.setS3KeyPrefix(StringUtils.appendIfMissing(s3KeyPrefix, "/"));
// When listing S3 files, we ignore 0 byte objects that represent S3 directories.
List s3ObjectSummaries = s3Service.listDirectory(params, true);
// Fail registration if no storage files were discovered.
if (CollectionUtils.isEmpty(s3ObjectSummaries))
{
throw new ObjectNotFoundException(String.format("Found no files at \"s3://%s/%s\" location.", params.getS3BucketName(), params.getS3KeyPrefix()));
}
return storageFileHelper.createStorageFilesFromS3ObjectSummaries(s3ObjectSummaries);
}
/**
* Returns a Cartesian product of the lists of values specified.
*
* @param lists the lists of values
*
* @return the Cartesian product
*/
private List getCrossProduct(Map> lists)
{
List results = new ArrayList<>();
getCrossProduct(results, lists, 0, new String[lists.size()]);
return results;
}
/**
* A helper function used to compute a Cartesian product of the lists of values specified.
*/
private void getCrossProduct(List results, Map> lists, int depth, String[] current)
{
for (int i = 0; i < lists.get(depth).size(); i++)
{
current[depth] = lists.get(depth).get(i);
if (depth < lists.keySet().size() - 1)
{
getCrossProduct(results, lists, depth + 1, current);
}
else
{
results.add(Arrays.copyOf(current, current.length));
}
}
}
/**
* Gets the file validation parameters that can be used for getting a list of files by the S3 service. The returned DTO will contain the expected S3 key
* prefix when the "validate path prefix" flag is set or it will contain the directory of the storage entity if not.
*
* @param storageEntity the storage entity.
* @param expectedS3KeyPrefix the expected key prefix.
* @param storageUnitEntity the storage unit entity.
* @param validatePathPrefix the validate path prefix flag.
*
* @return the parameters.
* @throws IllegalArgumentException if the "validate path prefix" flag is not present and no directory is configured on the storage entity.
*/
private S3FileTransferRequestParamsDto getFileValidationParams(StorageEntity storageEntity, String expectedS3KeyPrefix, StorageUnitEntity storageUnitEntity,
boolean validatePathPrefix) throws IllegalArgumentException
{
// Use the expected S3 key prefix by default (which is set when the validatePathPrefix flag is set).
String actualFileS3KeyPrefix = expectedS3KeyPrefix;
// In the case where the validate path prefix flag isn't set, we will either use the specified directory if it exists or it's an error
// since we have no way of knowing how to validate the files. It isn't reasonable to get a list of all files from S3 individually one by
// one since this would cause performance problems if a large number of files are present. Getting all files within a single key
// prefix is reasonable on the other hand since we can list all files at once starting at the key prefix.
if (!validatePathPrefix)
{
if (StringUtils.isNotBlank(storageUnitEntity.getDirectoryPath()))
{
actualFileS3KeyPrefix = storageUnitEntity.getDirectoryPath();
}
else
{
throw new IllegalArgumentException("Unable to validate file existence because storage \"" + storageEntity.getName() +
"\" does not validate path prefix and storage unit doesn't have a directory configured.");
}
}
// Add a trailing backslash if it doesn't already exist.
actualFileS3KeyPrefix = StringUtils.appendIfMissing(actualFileS3KeyPrefix, "/");
// Get S3 bucket access parameters and set the actual key prefix.
S3FileTransferRequestParamsDto params = storageHelper.getS3BucketAccessParams(storageEntity);
params.setS3KeyPrefix(actualFileS3KeyPrefix);
// Return the newly created parameters.
return params;
}
private String getLatestPartitionValueNotFoundErrorMessage(String boundType, String boundPartitionValue, String partitionKey,
BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion, List storageEntities)
{
// Get the list of storage names, if storage entities are specified.
List storageNames = storageEntities.stream().map(StorageEntity::getName).collect(Collectors.toList());
// Build and return error message.
return String.format("Failed to find partition value which is the latest %s partition value = \"%s\" for partition key = \"%s\" due to " +
"no available business object data in \"%s\" storage that satisfies the search criteria. " +
"Business object data {namespace: \"%s\", businessObjectDefinitionName: \"%s\", businessObjectFormatUsage: \"%s\", " +
"businessObjectFormatFileType: \"%s\", businessObjectFormatVersion: %d, businessObjectDataVersion: %d}", boundType, boundPartitionValue,
partitionKey, StringUtils.join(storageNames, ","), businessObjectFormatKey.getNamespace(),
businessObjectFormatKey.getBusinessObjectDefinitionName(), businessObjectFormatKey.getBusinessObjectFormatUsage(),
businessObjectFormatKey.getBusinessObjectFormatFileType(), businessObjectFormatKey.getBusinessObjectFormatVersion(), businessObjectDataVersion);
}
/**
* Returns the partition key column position (one-based numbering).
*
* @param partitionKey the partition key
* @param businessObjectFormatEntity, the business object format entity
*
* @return the partition key column position
* @throws IllegalArgumentException if partition key is not found in schema
*/
private int getPartitionColumnPosition(String partitionKey, BusinessObjectFormatEntity businessObjectFormatEntity)
{
int partitionColumnPosition = 0;
if (partitionKey.equalsIgnoreCase(businessObjectFormatEntity.getPartitionKey()))
{
partitionColumnPosition = BusinessObjectDataEntity.FIRST_PARTITION_COLUMN_POSITION;
}
else
{
// Get business object format model object to directly access schema columns and partitions.
BusinessObjectFormat businessObjectFormat = businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity);
Assert.notNull(businessObjectFormat.getSchema(), String.format(
"Partition key \"%s\" doesn't match configured business object format partition key \"%s\" and " +
"there is no schema defined to check subpartition columns for business object format {%s}.", partitionKey,
businessObjectFormatEntity.getPartitionKey(), businessObjectFormatHelper.businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity)));
// Get an upper boundary for the set of partition columns that we will check while searching for partition key.
int upperBoundary =
Math.min(BusinessObjectDataEntity.MAX_SUBPARTITIONS + 1, CollectionUtils.size(businessObjectFormat.getSchema().getPartitions()));
// Search for partition key in schema.
for (int i = 0; i < upperBoundary; i++)
{
if (partitionKey.equalsIgnoreCase(businessObjectFormat.getSchema().getPartitions().get(i).getName()))
{
// Partition key found in schema.
partitionColumnPosition = i + 1;
break;
}
}
Assert.isTrue(partitionColumnPosition > 0, String
.format("The partition key \"%s\" does not exist in first %d partition columns in the schema for business object format {%s}.", partitionKey,
BusinessObjectDataEntity.MAX_SUBPARTITIONS + 1,
businessObjectFormatHelper.businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity)));
}
return partitionColumnPosition;
}
private String getPartitionValueNotFoundErrorMessage(String partitionValueType, String partitionKey, BusinessObjectFormatKey businessObjectFormatKey,
Integer businessObjectDataVersion, List storageEntities)
{
// Get the list of storage names, if storage entities are specified.
List storageNames = storageEntities.stream().map(StorageEntity::getName).collect(Collectors.toList());
// Build and return error message.
return String.format("Failed to find %s partition value for partition key = \"%s\" due to " +
"no available business object data in \"%s\" storage(s) that is registered using that partition. " +
"Business object data {namespace: \"%s\", businessObjectDefinitionName: \"%s\", businessObjectFormatUsage: \"%s\", " +
"businessObjectFormatFileType: \"%s\", businessObjectFormatVersion: %d, businessObjectDataVersion: %d}", partitionValueType, partitionKey,
StringUtils.join(storageNames, ","), businessObjectFormatKey.getNamespace(), businessObjectFormatKey.getBusinessObjectDefinitionName(),
businessObjectFormatKey.getBusinessObjectFormatUsage(), businessObjectFormatKey.getBusinessObjectFormatFileType(),
businessObjectFormatKey.getBusinessObjectFormatVersion(), businessObjectDataVersion);
}
/**
* Gets the partition values to process.
*
* @param partitionValueFilters the list of partition values.
* @param standalonePartitionValueFilter the standalone partition value.
*
* @return the list of partition values to process.
*/
private List getPartitionValuesToProcess(List partitionValueFilters,
PartitionValueFilter standalonePartitionValueFilter)
{
// Build a list of partition value filters to process based on the specified partition value filters.
List partitionValueFiltersToProcess = new ArrayList<>();
if (partitionValueFilters != null)
{
partitionValueFiltersToProcess.addAll(partitionValueFilters);
}
if (standalonePartitionValueFilter != null)
{
partitionValueFiltersToProcess.add(standalonePartitionValueFilter);
}
return partitionValueFiltersToProcess;
}
/**
* Builds a list of partition values from a "partition value list" partition value filter option. Duplicates will be removed and the list will be ordered
* ascending.
*
* @param partitionValues the partition values passed in the partition value list filter option
* @param partitionKey the partition key
* @param partitionColumnPosition the partition column position (one-based numbering)
* @param businessObjectFormatKey the business object format key
* @param businessObjectDataVersion the business object data version
* @param storageEntities the optional list of storage entities
* @param storagePlatformEntity the optional storage platform entity, e.g. S3 for Hive DDL. It is ignored when the list of storage entities is not empty
* @param excludedStoragePlatformEntity the optional storage platform entity to be excluded from search. It is ignored when the list of storage entities is
* not empty or the storage platform entity is specified
*
* @return the unique and sorted partition value list
*/
private List processPartitionValueListFilterOption(List partitionValues, String partitionKey, int partitionColumnPosition,
BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion, List storageEntities,
StoragePlatformEntity storagePlatformEntity, StoragePlatformEntity excludedStoragePlatformEntity)
{
List resultPartitionValues = new ArrayList<>();
// Remove any duplicates and sort the request partition values.
List uniqueAndSortedPartitionValues = new ArrayList<>();
uniqueAndSortedPartitionValues.addAll(new TreeSet<>(partitionValues));
// This flag to be used to indicate if we updated partition value list per special partition value tokens.
boolean partitionValueListUpdated = false;
// If the maximum partition value token is specified, substitute special partition value token with the actual partition value.
// If a business object data version isn't specified, the latest VALID business object data version will be used.
if (uniqueAndSortedPartitionValues.contains(BusinessObjectDataService.MAX_PARTITION_VALUE_TOKEN))
{
// Get business object data status entity for the VALID status.
BusinessObjectDataStatusEntity validBusinessObjectDataStatusEntity =
businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(BusinessObjectDataStatusEntity.VALID);
// Get the maximum partition value.
String maxPartitionValue = businessObjectDataDao
.getBusinessObjectDataMaxPartitionValue(partitionColumnPosition, businessObjectFormatKey, businessObjectDataVersion,
validBusinessObjectDataStatusEntity, storageEntities, storagePlatformEntity, excludedStoragePlatformEntity, null, null);
if (maxPartitionValue == null)
{
throw new ObjectNotFoundException(
getPartitionValueNotFoundErrorMessage("maximum", partitionKey, businessObjectFormatKey, businessObjectDataVersion, storageEntities));
}
uniqueAndSortedPartitionValues.remove(BusinessObjectDataService.MAX_PARTITION_VALUE_TOKEN);
uniqueAndSortedPartitionValues.add(maxPartitionValue);
partitionValueListUpdated = true;
}
// If the minimum partition value token is specified, substitute special partition value token with the actual partition value.
// If a business object data version isn't specified, the latest VALID business object data version will be used.
if (uniqueAndSortedPartitionValues.contains(BusinessObjectDataService.MIN_PARTITION_VALUE_TOKEN))
{
// Get business object data status entity for the VALID status.
BusinessObjectDataStatusEntity validBusinessObjectDataStatusEntity =
businessObjectDataStatusDaoHelper.getBusinessObjectDataStatusEntity(BusinessObjectDataStatusEntity.VALID);
// Get the minimum partition value.
String minPartitionValue = businessObjectDataDao
.getBusinessObjectDataMinPartitionValue(partitionColumnPosition, businessObjectFormatKey, businessObjectDataVersion,
validBusinessObjectDataStatusEntity, storageEntities, storagePlatformEntity, excludedStoragePlatformEntity);
if (minPartitionValue == null)
{
throw new ObjectNotFoundException(
getPartitionValueNotFoundErrorMessage("minimum", partitionKey, businessObjectFormatKey, businessObjectDataVersion, storageEntities));
}
uniqueAndSortedPartitionValues.remove(BusinessObjectDataService.MIN_PARTITION_VALUE_TOKEN);
uniqueAndSortedPartitionValues.add(minPartitionValue);
partitionValueListUpdated = true;
}
// Build the final list of partition values.
if (partitionValueListUpdated)
{
// Remove any duplicates and sort the list of partition values is we updated the list per special partition value tokens.
resultPartitionValues.addAll(new TreeSet<>(uniqueAndSortedPartitionValues));
}
else
{
resultPartitionValues.addAll(uniqueAndSortedPartitionValues);
}
return resultPartitionValues;
}
/**
* Builds a list of partition values from a "partition value range" partition value filter option. The list of partition values will come from the expected
* partition values table for values within the specified range. The list will be ordered ascending.
*
* @param partitionValueRange the partition value range
* @param businessObjectFormatEntity the business object format entity
*
* @return the unique and sorted partition value list
*/
private List processPartitionValueRangeFilterOption(PartitionValueRange partitionValueRange, BusinessObjectFormatEntity businessObjectFormatEntity)
{
List resultPartitionValues = new ArrayList<>();
Assert.notNull(businessObjectFormatEntity.getPartitionKeyGroup(), String
.format("A partition key group, which is required to use partition value ranges, is not specified for the business object format {%s}.",
businessObjectFormatHelper.businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity)));
List expectedPartitionValueEntities = expectedPartitionValueDao
.getExpectedPartitionValuesByGroupAndRange(businessObjectFormatEntity.getPartitionKeyGroup().getPartitionKeyGroupName(), partitionValueRange);
// Populate the partition values returned from the range query.
for (ExpectedPartitionValueEntity expectedPartitionValueEntity : expectedPartitionValueEntities)
{
String partitionValue = expectedPartitionValueEntity.getPartitionValue();
// Validate that expected partition value does not match to one of the partition value tokens.
Assert.isTrue(!partitionValue.equals(BusinessObjectDataService.MAX_PARTITION_VALUE_TOKEN) &&
!partitionValue.equals(BusinessObjectDataService.MIN_PARTITION_VALUE_TOKEN),
"A partition value token cannot be specified as one of the expected partition values.");
resultPartitionValues.add(partitionValue);
}
// Validate that our partition value range results in a non-empty partition value list.
Assert.notEmpty(resultPartitionValues, String
.format("Partition value range [\"%s\", \"%s\"] contains no valid partition values in partition key group \"%s\". Business object format: {%s}",
partitionValueRange.getStartPartitionValue(), partitionValueRange.getEndPartitionValue(),
businessObjectFormatEntity.getPartitionKeyGroup().getPartitionKeyGroupName(),
businessObjectFormatHelper.businessObjectFormatEntityAltKeyToString(businessObjectFormatEntity)));
return resultPartitionValues;
}
/**
* Validates the business object data create request. This method also trims appropriate request parameters.
*
* @param request the request
* @param fileSizeRequired specifies if fileSizeBytes value is required or not
* @param businessObjectDataStatusEntity The status entity in the request
*
* @throws IllegalArgumentException if any validation errors were found.
*/
private void validateBusinessObjectDataCreateRequest(BusinessObjectDataCreateRequest request, boolean fileSizeRequired,
BusinessObjectDataStatusEntity businessObjectDataStatusEntity)
{
// Validate and trim the request parameters.
request.setNamespace(alternateKeyHelper.validateStringParameter("namespace", request.getNamespace()));
request.setBusinessObjectDefinitionName(
alternateKeyHelper.validateStringParameter("business object definition name", request.getBusinessObjectDefinitionName()));
request
.setBusinessObjectFormatUsage(alternateKeyHelper.validateStringParameter("business object format usage", request.getBusinessObjectFormatUsage()));
request.setBusinessObjectFormatFileType(
alternateKeyHelper.validateStringParameter("business object format file type", request.getBusinessObjectFormatFileType()));
Assert.notNull(request.getBusinessObjectFormatVersion(), "A business object format version must be specified.");
request.setPartitionKey(alternateKeyHelper.validateStringParameter("partition key", request.getPartitionKey()));
request.setPartitionValue(alternateKeyHelper.validateStringParameter("partition value", request.getPartitionValue()));
businessObjectDataHelper.validateSubPartitionValues(request.getSubPartitionValues());
Assert.isTrue(CollectionUtils.isNotEmpty(request.getStorageUnits()), "At least one storage unit must be specified.");
for (StorageUnitCreateRequest storageUnit : request.getStorageUnits())
{
Assert.notNull(storageUnit, "A storage unit can't be null.");
// Validate and trim the storage name.
Assert.hasText(storageUnit.getStorageName(), "A storage name is required for each storage unit.");
storageUnit.setStorageName(storageUnit.getStorageName().trim());
if (BooleanUtils.isTrue(storageUnit.isDiscoverStorageFiles()))
{
// The auto-discovery of storage files is enabled, thus a storage directory is required and storage files cannot be specified.
Assert.isTrue(storageUnit.getStorageDirectory() != null, "A storage directory must be specified when discovery of storage files is enabled.");
Assert.isTrue(CollectionUtils.isEmpty(storageUnit.getStorageFiles()),
"Storage files cannot be specified when discovery of storage files is enabled.");
}
else if (!Boolean.TRUE.equals(businessObjectDataStatusEntity.getPreRegistrationStatus()))
{
// Since auto-discovery is disabled, a storage directory or at least one storage file are required for each storage unit.
Assert.isTrue(storageUnit.getStorageDirectory() != null || CollectionUtils.isNotEmpty(storageUnit.getStorageFiles()),
"A storage directory or at least one storage file must be specified for each storage unit.");
}
// If storageDirectory element is present in the request, we require it to contain a non-empty directoryPath element.
if (storageUnit.getStorageDirectory() != null)
{
Assert.hasText(storageUnit.getStorageDirectory().getDirectoryPath(), "A storage directory path must be specified.");
storageUnit.getStorageDirectory().setDirectoryPath(storageUnit.getStorageDirectory().getDirectoryPath().trim());
}
if (CollectionUtils.isNotEmpty(storageUnit.getStorageFiles()))
{
for (StorageFile storageFile : storageUnit.getStorageFiles())
{
Assert.hasText(storageFile.getFilePath(), "A file path must be specified.");
storageFile.setFilePath(storageFile.getFilePath().trim());
if (fileSizeRequired)
{
Assert.notNull(storageFile.getFileSizeBytes(), "A file size must be specified.");
}
// Ensure row count is not negative.
if (storageFile.getRowCount() != null)
{
Assert.isTrue(storageFile.getRowCount() >= 0, "File \"" + storageFile.getFilePath() + "\" has a row count which is < 0.");
}
}
}
}
// Validate and trim the parents' keys.
validateBusinessObjectDataKeys(request.getBusinessObjectDataParents());
// Validate attributes.
attributeHelper.validateAttributes(request.getAttributes());
}
/**
* Validates the business object data keys. This will validate, trim, and make lowercase appropriate fields.
*
* @param keys the business object data keys to validate
*/
public void validateBusinessObjectDataKeys(List keys)
{
// Create a cloned business object data keys list where all keys are lowercase. This will be used for ensuring no duplicates are present in a
// case-insensitive way.
List businessObjectDataLowercaseKeys = new ArrayList<>();
if (CollectionUtils.isNotEmpty(keys))
{
for (BusinessObjectDataKey key : keys)
{
Assert.notNull(key, "A business object data key must be specified.");
// Validate and trim the alternate key parameter values.
key.setNamespace(alternateKeyHelper.validateStringParameter("namespace", key.getNamespace()));
key.setBusinessObjectDefinitionName(
alternateKeyHelper.validateStringParameter("business object definition name", key.getBusinessObjectDefinitionName()));
key.setBusinessObjectFormatUsage(
alternateKeyHelper.validateStringParameter("business object format usage", key.getBusinessObjectFormatUsage()));
key.setBusinessObjectFormatFileType(
alternateKeyHelper.validateStringParameter("business object format file type", key.getBusinessObjectFormatFileType()));
Assert.notNull(key.getBusinessObjectFormatVersion(), "A business object format version must be specified.");
key.setPartitionValue(alternateKeyHelper.validateStringParameter("partition value", key.getPartitionValue()));
businessObjectDataHelper.validateSubPartitionValues(key.getSubPartitionValues());
Assert.notNull(key.getBusinessObjectDataVersion(), "A business object data version must be specified.");
// Add a lowercase clone to the lowercase key list.
businessObjectDataLowercaseKeys.add(cloneToLowerCase(key));
}
}
// Check for duplicates by ensuring the lowercase list size and the hash set (removes duplicates) size are the same.
if (businessObjectDataLowercaseKeys.size() != new HashSet<>(businessObjectDataLowercaseKeys).size())
{
throw new IllegalArgumentException("Business object data keys can not contain duplicates.");
}
}
}