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_CREATE; import static org.finra.herd.model.dto.SearchIndexUpdateDto.SEARCH_INDEX_UPDATE_TYPE_DELETE; import static org.finra.herd.model.dto.SearchIndexUpdateDto.SEARCH_INDEX_UPDATE_TYPE_UPDATE; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import java.util.function.Predicate; import java.util.stream.Collectors; import com.google.common.collect.ImmutableSet; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.finra.herd.core.HerdDateUtils; import org.finra.herd.core.HerdStringUtils; import org.finra.herd.core.helper.ConfigurationHelper; import org.finra.herd.dao.BusinessObjectDefinitionDao; import org.finra.herd.dao.IndexFunctionsDao; import org.finra.herd.dao.config.DaoSpringModuleConfig; import org.finra.herd.dao.helper.TagDaoHelper; import org.finra.herd.model.annotation.NamespacePermission; import org.finra.herd.model.api.xml.Attribute; import org.finra.herd.model.api.xml.BusinessObjectDefinition; import org.finra.herd.model.api.xml.BusinessObjectDefinitionChangeEvent; import org.finra.herd.model.api.xml.BusinessObjectDefinitionCreateRequest; import org.finra.herd.model.api.xml.BusinessObjectDefinitionDescriptiveInformationUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectDefinitionKey; import org.finra.herd.model.api.xml.BusinessObjectDefinitionKeys; import org.finra.herd.model.api.xml.BusinessObjectDefinitionSearchFilter; import org.finra.herd.model.api.xml.BusinessObjectDefinitionSearchKey; import org.finra.herd.model.api.xml.BusinessObjectDefinitionSearchRequest; import org.finra.herd.model.api.xml.BusinessObjectDefinitionSearchResponse; import org.finra.herd.model.api.xml.BusinessObjectDefinitionUpdateRequest; import org.finra.herd.model.api.xml.BusinessObjectFormatKey; import org.finra.herd.model.api.xml.DescriptiveBusinessObjectFormat; import org.finra.herd.model.api.xml.DescriptiveBusinessObjectFormatUpdateRequest; import org.finra.herd.model.api.xml.NamespacePermissionEnum; import org.finra.herd.model.api.xml.SampleDataFile; import org.finra.herd.model.dto.BusinessObjectDefinitionSampleFileUpdateDto; import org.finra.herd.model.dto.ConfigurationValue; import org.finra.herd.model.dto.SearchIndexUpdateDto; import org.finra.herd.model.jpa.BusinessObjectDefinitionAttributeEntity; import org.finra.herd.model.jpa.BusinessObjectDefinitionEntity; import org.finra.herd.model.jpa.BusinessObjectDefinitionSampleDataFileEntity; import org.finra.herd.model.jpa.BusinessObjectFormatEntity; import org.finra.herd.model.jpa.StorageEntity; import org.finra.herd.model.jpa.TagEntity; import org.finra.herd.service.BusinessObjectDefinitionService; import org.finra.herd.service.FacetFieldValidationService; import org.finra.herd.service.SearchableService; 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.SearchIndexUpdateHelper; import org.finra.herd.service.helper.StorageDaoHelper; import org.finra.herd.service.helper.TagHelper; /** * The business object definition service implementation. */ @Service @Transactional(value = DaoSpringModuleConfig.HERD_TRANSACTION_MANAGER_BEAN_NAME) // TODO: Refactor the service implementation and remove the PMD suppress warning statement below. @SuppressWarnings("PMD.TooManyMethods") public class BusinessObjectDefinitionServiceImpl implements BusinessObjectDefinitionService, SearchableService, FacetFieldValidationService { private static final Logger LOGGER = LoggerFactory.getLogger(BusinessObjectDefinitionServiceImpl.class); /** * The size of the chunks to use when updating search index documents based on a list of ids. */ public static final int UPDATE_SEARCH_INDEX_DOCUMENT_CHUNK_SIZE = 100; @Autowired private AlternateKeyHelper alternateKeyHelper; @Autowired private AttributeHelper attributeHelper; @Autowired private BusinessObjectDefinitionDao businessObjectDefinitionDao; @Autowired private BusinessObjectDefinitionDaoHelper businessObjectDefinitionDaoHelper; @Autowired private BusinessObjectDefinitionHelper businessObjectDefinitionHelper; @Autowired private BusinessObjectFormatDaoHelper businessObjectFormatDaoHelper; @Autowired private ConfigurationHelper configurationHelper; @Autowired private IndexFunctionsDao indexFunctionsDao; @Autowired private TagHelper tagHelper; @Autowired private TagDaoHelper tagDaoHelper; @Autowired private StorageDaoHelper storageDaoHelper; @Autowired private SearchIndexUpdateHelper searchIndexUpdateHelper; // Constant to hold the data provider name option for the business object definition search private static final String DATA_PROVIDER_NAME_FIELD = "dataprovidername"; // Constant to hold the short description option for the business object definition search private static final String SHORT_DESCRIPTION_FIELD = "shortdescription"; // Constant to hold the display name option for the business object definition search private static final String DISPLAY_NAME_FIELD = "displayname"; private static final String TAG_FACET_FIELD = "tag"; /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @NamespacePermission(fields = "#request.namespace", permissions = NamespacePermissionEnum.WRITE) @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectDefinition createBusinessObjectDefinition(BusinessObjectDefinitionCreateRequest request) { return createBusinessObjectDefinitionImpl(request); } /** * Creates a new business object definition. * * @param request the business object definition create request * * @return the created business object definition */ protected BusinessObjectDefinition createBusinessObjectDefinitionImpl( BusinessObjectDefinitionCreateRequest request) { BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .createBusinessObjectDefinitionEntity(request); // Notify the search index that a business object definition must be created. LOGGER.info( "Create the business object definition in the search index associated with the business object definition being created." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_CREATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_CREATE); // Create and return the business object definition object from the persisted entity. return createBusinessObjectDefinitionFromEntity(businessObjectDefinitionEntity, false); } @Override public boolean indexSizeCheckValidationBusinessObjectDefinitions(String indexName) { final String documentType = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_DOCUMENT_TYPE, String.class); // Simple count validation, index size should equal entity list size final long indexSize = indexFunctionsDao.getNumberOfTypesInIndex(indexName, documentType); final long businessObjectDefinitionDatabaseTableSize = businessObjectDefinitionDao .getCountOfAllBusinessObjectDefinitions(); if (businessObjectDefinitionDatabaseTableSize != indexSize) { LOGGER.error( "Index validation failed, business object definition database table size {}, does not equal index size {}.", businessObjectDefinitionDatabaseTableSize, indexSize); } return businessObjectDefinitionDatabaseTableSize == indexSize; } @Override public boolean indexSpotCheckPercentageValidationBusinessObjectDefinitions(String indexName) { final Double spotCheckPercentage = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_SPOT_CHECK_PERCENTAGE, Double.class); // Get a list of all business object definitions final List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntityList = Collections .unmodifiableList(businessObjectDefinitionDao .getPercentageOfAllBusinessObjectDefinitions(spotCheckPercentage)); return indexValidateBusinessObjectDefinitionsList(businessObjectDefinitionEntityList, indexName); } @Override public boolean indexSpotCheckMostRecentValidationBusinessObjectDefinitions(String indexName) { final Integer spotCheckMostRecentNumber = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_SPOT_CHECK_MOST_RECENT_NUMBER, Integer.class); // Get a list of all business object definitions final List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntityList = Collections .unmodifiableList(businessObjectDefinitionDao .getMostRecentBusinessObjectDefinitions(spotCheckMostRecentNumber)); return indexValidateBusinessObjectDefinitionsList(businessObjectDefinitionEntityList, indexName); } @Override @Async public Future<Void> indexValidateAllBusinessObjectDefinitions(String indexName) { final String documentType = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_DOCUMENT_TYPE, String.class); // Get a list of all business object definitions final List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntityList = Collections .unmodifiableList(businessObjectDefinitionDao.getAllBusinessObjectDefinitions()); // Remove any index documents that are not in the database removeAnyIndexDocumentsThatAreNotInBusinessObjectsDefinitionsList(indexName, documentType, businessObjectDefinitionEntityList); // Validate all Business Object Definitions businessObjectDefinitionHelper.executeFunctionForBusinessObjectDefinitionEntities(indexName, documentType, businessObjectDefinitionEntityList, indexFunctionsDao::validateDocumentIndex); // Return an AsyncResult so callers will know the future is "done". They can call "isDone" to know when this method has completed and they // can call "get" to see if any exceptions were thrown. return new AsyncResult<>(null); } /** * Method to remove business object definitions in the index that don't exist in the database * * @param indexName the name of the index * @param documentType the document type * @param businessObjectDefinitionEntityList list of business object definitions in the database */ private void removeAnyIndexDocumentsThatAreNotInBusinessObjectsDefinitionsList(final String indexName, final String documentType, List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntityList) { // Get a list of business object definition ids from the list of business object definition entities in the database List<String> databaseBusinessObjectDefinitionIdList = new ArrayList<>(); businessObjectDefinitionEntityList .forEach(businessObjectDefinitionEntity -> databaseBusinessObjectDefinitionIdList .add(businessObjectDefinitionEntity.getId().toString())); // Get a list of business object definition ids in the search index List<String> indexDocumentBusinessObjectDefinitionIdList = indexFunctionsDao.getIdsInIndex(indexName, documentType); // Remove the database ids from the index ids indexDocumentBusinessObjectDefinitionIdList.removeAll(databaseBusinessObjectDefinitionIdList); // If there are any ids left in the index list they need to be removed indexDocumentBusinessObjectDefinitionIdList .forEach(id -> indexFunctionsDao.deleteDocumentById(indexName, documentType, id)); } /** * A helper method that will validate a list of business object definitions * * @param businessObjectDefinitionEntityList the list of business object definitions that will be validated * * @return true all of the business object definitions are valid in the index */ private boolean indexValidateBusinessObjectDefinitionsList( final List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntityList, String indexName) { final String documentType = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_DOCUMENT_TYPE, String.class); Predicate<BusinessObjectDefinitionEntity> validInIndexPredicate = businessObjectDefinitionEntity -> { // Fetch Join with .size() businessObjectDefinitionEntity.getAttributes().size(); businessObjectDefinitionEntity.getBusinessObjectDefinitionTags().size(); businessObjectDefinitionEntity.getBusinessObjectFormats().size(); businessObjectDefinitionEntity.getColumns().size(); businessObjectDefinitionEntity.getSampleDataFiles().size(); // Convert the business object definition entity to a JSON string final String jsonString = businessObjectDefinitionHelper .safeObjectMapperWriteValueAsString(businessObjectDefinitionEntity); return indexFunctionsDao.isValidDocumentIndex(indexName, documentType, businessObjectDefinitionEntity.getId().toString(), jsonString); }; boolean isValid = true; for (BusinessObjectDefinitionEntity businessObjectDefinitionEntity : businessObjectDefinitionEntityList) { if (!validInIndexPredicate.test(businessObjectDefinitionEntity)) { isValid = false; } } return isValid; } /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @NamespacePermission(fields = "#businessObjectDefinitionKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectDefinition updateBusinessObjectDefinition( BusinessObjectDefinitionKey businessObjectDefinitionKey, BusinessObjectDefinitionUpdateRequest request) { return updateBusinessObjectDefinitionImpl(businessObjectDefinitionKey, request); } /** * Updates a business object definition. * * @param businessObjectDefinitionKey the business object definition key * @param request the business object definition update request * * @return the updated business object definition */ protected BusinessObjectDefinition updateBusinessObjectDefinitionImpl( BusinessObjectDefinitionKey businessObjectDefinitionKey, BusinessObjectDefinitionUpdateRequest request) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); validateBusinessObjectDefinitionUpdateRequest(request); // Retrieve and ensure that a business object definition already exists with the specified key. BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); // Update and persist the entity. updateBusinessObjectDefinitionEntity(businessObjectDefinitionEntity, request); // 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 being updated." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_UPDATE); // Create and return the business object definition object from the persisted entity. return createBusinessObjectDefinitionFromEntity(businessObjectDefinitionEntity, false); } /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @NamespacePermission(fields = "#businessObjectDefinitionKey.namespace", permissions = { NamespacePermissionEnum.WRITE_DESCRIPTIVE_CONTENT, NamespacePermissionEnum.WRITE }) @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectDefinition updateBusinessObjectDefinitionDescriptiveInformation( BusinessObjectDefinitionKey businessObjectDefinitionKey, BusinessObjectDefinitionDescriptiveInformationUpdateRequest request) { return updateBusinessObjectDefinitionDescriptiveInformationImpl(businessObjectDefinitionKey, request); } /** * Updates a business object definition descriptive information. * * @param businessObjectDefinitionKey the business object definition key * @param request the business object definition descriptive information update request * * @return the updated business object definition */ protected BusinessObjectDefinition updateBusinessObjectDefinitionDescriptiveInformationImpl( BusinessObjectDefinitionKey businessObjectDefinitionKey, BusinessObjectDefinitionDescriptiveInformationUpdateRequest request) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); validateBusinessObjectDefinitionDescriptiveInformationUpdateRequest(request); // Retrieve and ensure that a business object definition already exists with the specified key. BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); BusinessObjectFormatEntity businessObjectFormatEntity = null; DescriptiveBusinessObjectFormatUpdateRequest descriptiveFormat = request .getDescriptiveBusinessObjectFormat(); if (descriptiveFormat != null) { BusinessObjectFormatKey businessObjectFormatKey = new BusinessObjectFormatKey(); businessObjectFormatKey.setBusinessObjectDefinitionName(businessObjectDefinitionEntity.getName()); businessObjectFormatKey.setNamespace(businessObjectDefinitionEntity.getNamespace().getCode()); businessObjectFormatKey .setBusinessObjectFormatFileType(descriptiveFormat.getBusinessObjectFormatFileType()); businessObjectFormatKey.setBusinessObjectFormatUsage(descriptiveFormat.getBusinessObjectFormatUsage()); businessObjectFormatEntity = businessObjectFormatDaoHelper .getBusinessObjectFormatEntity(businessObjectFormatKey); } businessObjectDefinitionEntity.setDescriptiveBusinessObjectFormat(businessObjectFormatEntity); // Update and persist the entity. updateBusinessObjectDefinitionEntityDescriptiveInformation(businessObjectDefinitionEntity, request); // 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 being updated." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_UPDATE); // Create and return the business object definition object from the persisted entity. return createBusinessObjectDefinitionFromEntity(businessObjectDefinitionEntity, false); } /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectDefinition getBusinessObjectDefinition( BusinessObjectDefinitionKey businessObjectDefinitionKey, Boolean includeBusinessObjectDefinitionUpdateHistory) { return getBusinessObjectDefinitionImpl(businessObjectDefinitionKey, includeBusinessObjectDefinitionUpdateHistory); } /** * Gets a business object definition for the specified key. * * @param businessObjectDefinitionKey the business object definition key * @param includeBusinessObjectDefinitionUpdateHistory a flag to include change event information or not * * @return the business object definition. */ protected BusinessObjectDefinition getBusinessObjectDefinitionImpl( BusinessObjectDefinitionKey businessObjectDefinitionKey, Boolean includeBusinessObjectDefinitionUpdateHistory) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); // Retrieve and ensure that a business object definition already exists with the specified key. BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); // Create and return the business object definition object from the persisted entity. return createBusinessObjectDefinitionFromEntity(businessObjectDefinitionEntity, includeBusinessObjectDefinitionUpdateHistory); } /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @NamespacePermission(fields = "#businessObjectDefinitionKey.namespace", permissions = NamespacePermissionEnum.WRITE) @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public BusinessObjectDefinition deleteBusinessObjectDefinition( BusinessObjectDefinitionKey businessObjectDefinitionKey) { return deleteBusinessObjectDefinitionImpl(businessObjectDefinitionKey); } /** * Deletes a business object definition for the specified name. * * @param businessObjectDefinitionKey the business object definition key * * @return the business object definition that was deleted. */ protected BusinessObjectDefinition deleteBusinessObjectDefinitionImpl( BusinessObjectDefinitionKey businessObjectDefinitionKey) { // Perform validation and trim. businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); // Retrieve and ensure that a business object definition already exists with the specified key. BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); // Delete the business object definition. businessObjectDefinitionDao.delete(businessObjectDefinitionEntity); // Notify the search index that a business object definition must be deleted. LOGGER.info( "Delete the business object definition in the search index associated with the business object definition being deleted." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_DELETE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_DELETE); // Create and return the business object definition object from the deleted entity. return createBusinessObjectDefinitionFromEntity(businessObjectDefinitionEntity, false); } @Override public BusinessObjectDefinitionKeys getBusinessObjectDefinitions() { BusinessObjectDefinitionKeys businessObjectDefinitionKeys = new BusinessObjectDefinitionKeys(); businessObjectDefinitionKeys.getBusinessObjectDefinitionKeys() .addAll(businessObjectDefinitionDao.getBusinessObjectDefinitionKeys()); return businessObjectDefinitionKeys; } @Override public BusinessObjectDefinitionKeys getBusinessObjectDefinitions(String namespaceCode) { // Validate and trim the namespace code. Assert.hasText(namespaceCode, "A namespace must be specified."); // Retrieve and return the list of business object definitions BusinessObjectDefinitionKeys businessObjectDefinitionKeys = new BusinessObjectDefinitionKeys(); businessObjectDefinitionKeys.getBusinessObjectDefinitionKeys().addAll( businessObjectDefinitionDao.getBusinessObjectDefinitionKeysByNamespace(namespaceCode.trim())); return businessObjectDefinitionKeys; } @Override public Set<String> getValidFacetFields() { return ImmutableSet.of(TAG_FACET_FIELD); } @Override public BusinessObjectDefinitionSearchResponse searchBusinessObjectDefinitions( BusinessObjectDefinitionSearchRequest request, Set<String> fields) { // Validate the business object definition search fields. validateSearchResponseFields(fields); List<TagEntity> tagEntities = new ArrayList<>(); if (!CollectionUtils.isEmpty(request.getBusinessObjectDefinitionSearchFilters())) { // Validate the search request. validateBusinessObjectDefinitionSearchRequest(request); BusinessObjectDefinitionSearchKey businessObjectDefinitionSearchKey = request .getBusinessObjectDefinitionSearchFilters().get(0).getBusinessObjectDefinitionSearchKeys() .get(0); TagEntity tagEntity = tagDaoHelper.getTagEntity(businessObjectDefinitionSearchKey.getTagKey()); // If includeTagHierarchy is true, get list of children tag entities down the hierarchy of the specified tag. tagEntities.add(tagEntity); if (BooleanUtils.isTrue(businessObjectDefinitionSearchKey.isIncludeTagHierarchy())) { tagEntities.addAll(tagDaoHelper.getTagChildrenEntities(tagEntity)); } } // Construct business object search response. BusinessObjectDefinitionSearchResponse searchResponse = new BusinessObjectDefinitionSearchResponse(); List<BusinessObjectDefinition> businessObjectDefinitions = new ArrayList<>(); searchResponse.setBusinessObjectDefinitions(businessObjectDefinitions); // Retrieve all unique business object definition entities and construct a list of business object definitions based on the requested fields. for (BusinessObjectDefinitionEntity businessObjectDefinition : ImmutableSet .copyOf(businessObjectDefinitionDao.getBusinessObjectDefinitions(tagEntities))) { businessObjectDefinitions .add(createBusinessObjectDefinitionFromEntity(businessObjectDefinition, fields)); } return searchResponse; } /** * Validates the business object definition update request. This method also trims request parameters. * * @param request the request. * * @throws IllegalArgumentException if any validation errors were found. */ private void validateBusinessObjectDefinitionUpdateRequest(BusinessObjectDefinitionUpdateRequest request) { if (request.getDisplayName() != null) { request.setDisplayName(request.getDisplayName().trim()); } // Validate attributes. attributeHelper.validateAttributes(request.getAttributes()); } /** * Validates the business object definition update request. This method also trims request parameters. * * @param request the request. * * @throws IllegalArgumentException if any validation errors were found. */ private void validateBusinessObjectDefinitionDescriptiveInformationUpdateRequest( BusinessObjectDefinitionDescriptiveInformationUpdateRequest request) { if (request.getDisplayName() != null) { request.setDisplayName(request.getDisplayName().trim()); } if (request.getDescriptiveBusinessObjectFormat() != null) { DescriptiveBusinessObjectFormatUpdateRequest descriptiveFormat = request .getDescriptiveBusinessObjectFormat(); descriptiveFormat.setBusinessObjectFormatUsage(alternateKeyHelper.validateStringParameter( "business object format usage", descriptiveFormat.getBusinessObjectFormatUsage())); descriptiveFormat.setBusinessObjectFormatFileType(alternateKeyHelper.validateStringParameter( "business object format file type", descriptiveFormat.getBusinessObjectFormatFileType())); } } /** * Update and persist the business object definition per specified update request. * * @param businessObjectDefinitionEntity the business object definition entity * @param request the business object definition update request */ private void updateBusinessObjectDefinitionEntity(BusinessObjectDefinitionEntity businessObjectDefinitionEntity, BusinessObjectDefinitionUpdateRequest request) { // Update the entity with the new description value. businessObjectDefinitionEntity.setDescription(request.getDescription()); businessObjectDefinitionEntity.setDisplayName(request.getDisplayName()); // Update the attributes. // Load all existing attribute entities in a map with a "lowercase" attribute name as the key for case insensitivity. Map<String, BusinessObjectDefinitionAttributeEntity> existingAttributeEntities = new HashMap<>(); for (BusinessObjectDefinitionAttributeEntity attributeEntity : businessObjectDefinitionEntity .getAttributes()) { String mapKey = attributeEntity.getName().toLowerCase(); if (existingAttributeEntities.containsKey(mapKey)) { throw new IllegalStateException(String.format( "Found duplicate attribute with name \"%s\" for business object definition {namespace: \"%s\", businessObjectDefinitionName: \"%s\"}.", mapKey, businessObjectDefinitionEntity.getNamespace().getCode(), businessObjectDefinitionEntity.getName())); } existingAttributeEntities.put(mapKey, attributeEntity); } // Process the list of attributes to determine that business object definition attribute entities should be created, updated, or deleted. List<BusinessObjectDefinitionAttributeEntity> createdAttributeEntities = new ArrayList<>(); List<BusinessObjectDefinitionAttributeEntity> retainedAttributeEntities = new ArrayList<>(); if (!CollectionUtils.isEmpty(request.getAttributes())) { for (Attribute attribute : request.getAttributes()) { // 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. BusinessObjectDefinitionAttributeEntity 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. BusinessObjectDefinitionAttributeEntity attributeEntity = new BusinessObjectDefinitionAttributeEntity(); businessObjectDefinitionEntity.getAttributes().add(attributeEntity); attributeEntity.setBusinessObjectDefinition(businessObjectDefinitionEntity); attributeEntity.setName(attribute.getName()); attributeEntity.setValue(attribute.getValue()); // Add this entity to the list of the newly created business object definition attribute entities. retainedAttributeEntities.add(attributeEntity); } } } // Remove any of the currently existing attribute entities that did not get onto the retained entities list. businessObjectDefinitionEntity.getAttributes().retainAll(retainedAttributeEntities); // Add all of the newly created business object definition attribute entities. businessObjectDefinitionEntity.getAttributes().addAll(createdAttributeEntities); // Persist the change event entity saveBusinessObjectDefinitionChangeEvents(businessObjectDefinitionEntity); // Persist the entity. businessObjectDefinitionDao.saveAndRefresh(businessObjectDefinitionEntity); } /** * Update and persist the business object definition descriptive information per specified update request. * * @param businessObjectDefinitionEntity the business object definition entity * @param request the business object definition update request */ private void updateBusinessObjectDefinitionEntityDescriptiveInformation( BusinessObjectDefinitionEntity businessObjectDefinitionEntity, BusinessObjectDefinitionDescriptiveInformationUpdateRequest request) { // Update the entity with the new description value. businessObjectDefinitionEntity.setDescription(request.getDescription()); businessObjectDefinitionEntity.setDisplayName(request.getDisplayName()); // Persist the change event entity saveBusinessObjectDefinitionChangeEvents(businessObjectDefinitionEntity); businessObjectDefinitionDao.saveAndRefresh(businessObjectDefinitionEntity); } /** * Update and persist the business object definition change events * * @param businessObjectDefinitionEntity the business object definition entity */ private void saveBusinessObjectDefinitionChangeEvents( BusinessObjectDefinitionEntity businessObjectDefinitionEntity) { businessObjectDefinitionDaoHelper.saveBusinessObjectDefinitionChangeEvents(businessObjectDefinitionEntity); } /** * Creates a business object definition from the persisted entity. * * @param businessObjectDefinitionEntity the business object definition entity * * @return the business object definition */ private BusinessObjectDefinition createBusinessObjectDefinitionFromEntity( BusinessObjectDefinitionEntity businessObjectDefinitionEntity, Boolean includeBusinessObjectDefinitionUpdateHistory) { // Create a business object definition. BusinessObjectDefinition businessObjectDefinition = new BusinessObjectDefinition(); businessObjectDefinition.setId(businessObjectDefinitionEntity.getId()); businessObjectDefinition.setNamespace(businessObjectDefinitionEntity.getNamespace().getCode()); businessObjectDefinition.setBusinessObjectDefinitionName(businessObjectDefinitionEntity.getName()); businessObjectDefinition.setDescription(businessObjectDefinitionEntity.getDescription()); businessObjectDefinition.setDataProviderName(businessObjectDefinitionEntity.getDataProvider().getName()); businessObjectDefinition.setDisplayName(businessObjectDefinitionEntity.getDisplayName()); // Add attributes. List<Attribute> attributes = new ArrayList<>(); businessObjectDefinition.setAttributes(attributes); for (BusinessObjectDefinitionAttributeEntity attributeEntity : businessObjectDefinitionEntity .getAttributes()) { attributes.add(new Attribute(attributeEntity.getName(), attributeEntity.getValue())); } if (businessObjectDefinitionEntity.getDescriptiveBusinessObjectFormat() != null) { BusinessObjectFormatEntity descriptiveFormatEntity = businessObjectDefinitionEntity .getDescriptiveBusinessObjectFormat(); DescriptiveBusinessObjectFormat descriptiveBusinessObjectFormat = new DescriptiveBusinessObjectFormat(); businessObjectDefinition.setDescriptiveBusinessObjectFormat(descriptiveBusinessObjectFormat); descriptiveBusinessObjectFormat.setBusinessObjectFormatUsage(descriptiveFormatEntity.getUsage()); descriptiveBusinessObjectFormat .setBusinessObjectFormatFileType(descriptiveFormatEntity.getFileType().getCode()); descriptiveBusinessObjectFormat .setBusinessObjectFormatVersion(descriptiveFormatEntity.getBusinessObjectFormatVersion()); } // Add sample data files. List<SampleDataFile> sampleDataFiles = new ArrayList<>(); businessObjectDefinition.setSampleDataFiles(sampleDataFiles); for (BusinessObjectDefinitionSampleDataFileEntity sampleDataFileEntity : businessObjectDefinitionEntity .getSampleDataFiles()) { sampleDataFiles.add(new SampleDataFile(sampleDataFileEntity.getDirectoryPath(), sampleDataFileEntity.getFileName())); } // Add auditable fields. businessObjectDefinition.setCreatedByUserId(businessObjectDefinitionEntity.getCreatedBy()); businessObjectDefinition.setLastUpdatedByUserId(businessObjectDefinitionEntity.getUpdatedBy()); businessObjectDefinition.setLastUpdatedOn( HerdDateUtils.getXMLGregorianCalendarValue(businessObjectDefinitionEntity.getUpdatedOn())); // Add change events. final List<BusinessObjectDefinitionChangeEvent> businessObjectDefinitionChangeEvents = new ArrayList<>(); if (BooleanUtils.isTrue(includeBusinessObjectDefinitionUpdateHistory)) { businessObjectDefinitionEntity.getChangeEvents().forEach(businessObjectDefinitionChangeEventEntity -> { DescriptiveBusinessObjectFormatUpdateRequest descriptiveBusinessObjectFormatUpdateRequest = null; if (businessObjectDefinitionChangeEventEntity.getFileType() != null) { descriptiveBusinessObjectFormatUpdateRequest = new DescriptiveBusinessObjectFormatUpdateRequest( businessObjectDefinitionChangeEventEntity.getUsage(), businessObjectDefinitionChangeEventEntity.getFileType()); } businessObjectDefinitionChangeEvents.add(new BusinessObjectDefinitionChangeEvent( businessObjectDefinitionChangeEventEntity.getDisplayName(), businessObjectDefinitionChangeEventEntity.getDescription(), descriptiveBusinessObjectFormatUpdateRequest, HerdDateUtils.getXMLGregorianCalendarValue( businessObjectDefinitionChangeEventEntity.getCreatedOn()), businessObjectDefinitionChangeEventEntity.getCreatedBy())); }); } businessObjectDefinition.setBusinessObjectDefinitionChangeEvents(businessObjectDefinitionChangeEvents); return businessObjectDefinition; } /** * Creates a light-weight business object definition from its persisted entity based on a set of requested fields. * * @param businessObjectDefinitionEntity the specified business object definition entity * @param fields the set of requested fields * * @return the light-weight business object definition */ private BusinessObjectDefinition createBusinessObjectDefinitionFromEntity( BusinessObjectDefinitionEntity businessObjectDefinitionEntity, Set<String> fields) { BusinessObjectDefinition definition = new BusinessObjectDefinition(); // Populate namespace and business object definition name fields by default definition.setNamespace(businessObjectDefinitionEntity.getNamespace().getCode()); definition.setBusinessObjectDefinitionName(businessObjectDefinitionEntity.getName()); // Decorate object with only the required fields if (fields.contains(DATA_PROVIDER_NAME_FIELD)) { definition.setDataProviderName(businessObjectDefinitionEntity.getDataProvider().getName()); } if (fields.contains(SHORT_DESCRIPTION_FIELD)) { // Get the configured value for short description's length Integer shortDescMaxLength = configurationHelper.getProperty( ConfigurationValue.BUSINESS_OBJECT_DEFINITION_SHORT_DESCRIPTION_LENGTH, Integer.class); definition.setShortDescription(HerdStringUtils .getShortDescription(businessObjectDefinitionEntity.getDescription(), shortDescMaxLength)); } if (fields.contains(DISPLAY_NAME_FIELD)) { definition.setDisplayName(businessObjectDefinitionEntity.getDisplayName()); } return definition; } @Override public Set<String> getValidSearchResponseFields() { return ImmutableSet.of(DATA_PROVIDER_NAME_FIELD, SHORT_DESCRIPTION_FIELD, DISPLAY_NAME_FIELD); } /** * Validate the business object definition search request. This method also trims the request parameters. * * @param businessObjectDefinitionSearchRequest the business object definition search request */ private void validateBusinessObjectDefinitionSearchRequest( BusinessObjectDefinitionSearchRequest businessObjectDefinitionSearchRequest) { if (CollectionUtils .size(businessObjectDefinitionSearchRequest.getBusinessObjectDefinitionSearchFilters()) == 1 && businessObjectDefinitionSearchRequest.getBusinessObjectDefinitionSearchFilters() .get(0) != null) { // Get the business object definition search filter. BusinessObjectDefinitionSearchFilter businessObjectDefinitionSearchFilter = businessObjectDefinitionSearchRequest .getBusinessObjectDefinitionSearchFilters().get(0); Assert.isTrue(CollectionUtils .size(businessObjectDefinitionSearchFilter.getBusinessObjectDefinitionSearchKeys()) == 1 && businessObjectDefinitionSearchFilter.getBusinessObjectDefinitionSearchKeys().get(0) != null, "Exactly one business object definition search key must be specified."); // Get the tag search key. BusinessObjectDefinitionSearchKey businessObjectDefinitionSearchKey = businessObjectDefinitionSearchFilter .getBusinessObjectDefinitionSearchKeys().get(0); tagHelper.validateTagKey(businessObjectDefinitionSearchKey.getTagKey()); } else { Assert.isTrue( CollectionUtils.size( businessObjectDefinitionSearchRequest.getBusinessObjectDefinitionSearchFilters()) == 1 && businessObjectDefinitionSearchRequest.getBusinessObjectDefinitionSearchFilters() .get(0) != null, "Exactly one business object definition search filter must be specified."); } } @Override public void updateBusinessObjectDefinitionEntitySampleFile( BusinessObjectDefinitionKey businessObjectDefinitionKey, BusinessObjectDefinitionSampleFileUpdateDto businessObjectDefinitionSampleFileUpdateDto) { String path = businessObjectDefinitionSampleFileUpdateDto.getPath(); String fileName = businessObjectDefinitionSampleFileUpdateDto.getFileName(); long fileSize = businessObjectDefinitionSampleFileUpdateDto.getFileSize(); // Validate business object key businessObjectDefinitionHelper.validateBusinessObjectDefinitionKey(businessObjectDefinitionKey); // Validate file name Assert.hasText(fileName, "A file name must be specified."); BusinessObjectDefinitionEntity businessObjectDefinitionEntity = businessObjectDefinitionDaoHelper .getBusinessObjectDefinitionEntity(businessObjectDefinitionKey); Collection<BusinessObjectDefinitionSampleDataFileEntity> sampleFiles = businessObjectDefinitionEntity .getSampleDataFiles(); boolean found = false; for (BusinessObjectDefinitionSampleDataFileEntity sampleDataFieEntity : sampleFiles) { // Assume the path is the same for this business object definition if (sampleDataFieEntity.getFileName().equals(fileName)) { found = true; sampleDataFieEntity.setFileSizeBytes(fileSize); businessObjectDefinitionDao.saveAndRefresh(businessObjectDefinitionEntity); break; } } // Create a new entity when not found if (!found) { StorageEntity storageEntity = storageDaoHelper.getStorageEntity(StorageEntity.SAMPLE_DATA_FILE_STORAGE); BusinessObjectDefinitionSampleDataFileEntity sampleDataFileEntity = new BusinessObjectDefinitionSampleDataFileEntity(); sampleDataFileEntity.setStorage(storageEntity); sampleDataFileEntity.setBusinessObjectDefinition(businessObjectDefinitionEntity); sampleDataFileEntity.setDirectoryPath(path); sampleDataFileEntity.setFileName(fileName); sampleDataFileEntity.setFileSizeBytes(fileSize); businessObjectDefinitionEntity.getSampleDataFiles().add(sampleDataFileEntity); businessObjectDefinitionDao.saveAndRefresh(businessObjectDefinitionEntity); } // 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 being updated." + " businessObjectDefinitionId=\"{}\", searchIndexUpdateType=\"{}\"", businessObjectDefinitionEntity.getId(), SEARCH_INDEX_UPDATE_TYPE_UPDATE); searchIndexUpdateHelper.modifyBusinessObjectDefinitionInSearchIndex(businessObjectDefinitionEntity, SEARCH_INDEX_UPDATE_TYPE_UPDATE); } /** * {@inheritDoc} * <p> * This implementation starts a new transaction. */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateSearchIndexDocumentBusinessObjectDefinition(SearchIndexUpdateDto searchIndexUpdateDto) { updateSearchIndexDocumentBusinessObjectDefinitionImpl(searchIndexUpdateDto); } /** * Updates the search index document representation of the business object definition. * * @param searchIndexUpdateDto the SearchIndexUpdateDto object */ protected void updateSearchIndexDocumentBusinessObjectDefinitionImpl( SearchIndexUpdateDto searchIndexUpdateDto) { final String indexName = configurationHelper.getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_INDEX_NAME, String.class); final String documentType = configurationHelper .getProperty(ConfigurationValue.ELASTICSEARCH_BDEF_DOCUMENT_TYPE, String.class); String modificationType = searchIndexUpdateDto.getModificationType(); List<Integer> ids = searchIndexUpdateDto.getBusinessObjectDefinitionIds(); LOGGER.info( "Updating the search index document representation(s) of the business object definition(s)." + " businessObjectDefinitionIds=[{}], searchIndexUpdateType=\"{}\"", ids.stream().map(String::valueOf).collect(Collectors.joining(", ")), modificationType); // Start at index 0 int fromIndex = 0; // Process documents until the ids are all updated while (fromIndex < ids.size()) { // Process based on a document chunk size, if the id.size is greater than the chunk size then use the chunk size int toIndex = ids.size() > fromIndex + UPDATE_SEARCH_INDEX_DOCUMENT_CHUNK_SIZE ? fromIndex + UPDATE_SEARCH_INDEX_DOCUMENT_CHUNK_SIZE : ids.size(); // Switch on the type of CRUD modification to be done switch (modificationType) { case SEARCH_INDEX_UPDATE_TYPE_CREATE: // Create a search index document indexFunctionsDao.createIndexDocuments(indexName, documentType, convertBusinessObjectDefinitionEntityListToJSONStringMap(businessObjectDefinitionDao .getAllBusinessObjectDefinitionsByIds(ids.subList(fromIndex, toIndex)))); break; case SEARCH_INDEX_UPDATE_TYPE_UPDATE: // Update a search index document indexFunctionsDao.updateIndexDocuments(indexName, documentType, convertBusinessObjectDefinitionEntityListToJSONStringMap(businessObjectDefinitionDao .getAllBusinessObjectDefinitionsByIds(ids.subList(fromIndex, toIndex)))); break; case SEARCH_INDEX_UPDATE_TYPE_DELETE: // Delete a search index document indexFunctionsDao.deleteIndexDocuments(indexName, documentType, ids.subList(fromIndex, toIndex)); break; default: LOGGER.warn("Unknown modification type received."); break; } // Set the from index to the toIndex fromIndex = toIndex; } LOGGER.info("Finished processing {} documents", ids.size()); } /** * Private method to convert a business object definition entity list to a list of JSON strings. * * @param businessObjectDefinitionEntities the list of business object definitions * * @return Map of key, business object definition ids, and value, business object definition entity as JSON string */ private Map<String, String> convertBusinessObjectDefinitionEntityListToJSONStringMap( List<BusinessObjectDefinitionEntity> businessObjectDefinitionEntities) { LOGGER.info("List size is {}.", businessObjectDefinitionEntities.size()); Map<String, String> businessObjectDefinitionJSONMap = new HashMap<>(); businessObjectDefinitionEntities.forEach(businessObjectDefinitionEntity -> { // Fetch Join with .size() businessObjectDefinitionEntity.getAttributes().size(); businessObjectDefinitionEntity.getBusinessObjectDefinitionTags().size(); businessObjectDefinitionEntity.getBusinessObjectFormats().size(); businessObjectDefinitionEntity.getColumns().size(); businessObjectDefinitionEntity.getSampleDataFiles().size(); String jsonString = businessObjectDefinitionHelper .safeObjectMapperWriteValueAsString(businessObjectDefinitionEntity); if (StringUtils.isNotEmpty(jsonString)) { businessObjectDefinitionJSONMap.put(businessObjectDefinitionEntity.getId().toString(), jsonString); } else { LOGGER.warn("Business Object Definition Entity {} jsonString is empty.", businessObjectDefinitionEntity.getId()); } }); return businessObjectDefinitionJSONMap; } }