/*
* 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 java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
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.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.finra.herd.dao.BusinessObjectDataDao;
import org.finra.herd.dao.BusinessObjectDataNotificationRegistrationDao;
import org.finra.herd.dao.BusinessObjectFormatDao;
import org.finra.herd.dao.StorageUnitNotificationRegistrationDao;
import org.finra.herd.dao.config.DaoSpringModuleConfig;
import org.finra.herd.model.api.xml.BusinessObjectData;
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.SchemaColumn;
import org.finra.herd.model.dto.BusinessObjectDataNotificationEventParamsDto;
import org.finra.herd.model.dto.NotificationEventParamsDto;
import org.finra.herd.model.dto.StorageUnitNotificationEventParamsDto;
import org.finra.herd.model.jpa.BusinessObjectDataEntity;
import org.finra.herd.model.jpa.BusinessObjectDataNotificationRegistrationEntity;
import org.finra.herd.model.jpa.BusinessObjectFormatEntity;
import org.finra.herd.model.jpa.NotificationActionEntity;
import org.finra.herd.model.jpa.NotificationEventTypeEntity;
import org.finra.herd.model.jpa.NotificationJobActionEntity;
import org.finra.herd.model.jpa.NotificationRegistrationStatusEntity;
import org.finra.herd.model.jpa.NotificationTypeEntity;
import org.finra.herd.model.jpa.StorageUnitEntity;
import org.finra.herd.model.jpa.StorageUnitNotificationRegistrationEntity;
import org.finra.herd.service.NotificationActionService;
import org.finra.herd.service.NotificationEventService;
import org.finra.herd.service.helper.BusinessObjectDataHelper;
import org.finra.herd.service.helper.BusinessObjectFormatHelper;
import org.finra.herd.service.helper.NotificationActionFactory;
/**
* The notification event service.
*/
@Service
@Transactional(value = DaoSpringModuleConfig.HERD_TRANSACTION_MANAGER_BEAN_NAME)
public class NotificationEventServiceImpl implements NotificationEventService
{
private static final Logger LOGGER = LoggerFactory.getLogger(NotificationEventServiceImpl.class);
@Autowired
private BusinessObjectDataDao businessObjectDataDao;
@Autowired
private BusinessObjectDataHelper businessObjectDataHelper;
@Autowired
private BusinessObjectDataNotificationRegistrationDao businessObjectDataNotificationRegistrationDao;
@Autowired
private BusinessObjectFormatDao businessObjectFormatDao;
@Autowired
private BusinessObjectFormatHelper businessObjectFormatHelper;
@Autowired
private NotificationActionFactory notificationActionFactory;
@Autowired
private StorageUnitNotificationRegistrationDao storageUnitNotificationRegistrationDao;
@Override
@Async
public Future<Void> processBusinessObjectDataNotificationEventAsync(NotificationEventTypeEntity.EventTypesBdata notificationEventType,
BusinessObjectDataKey businessObjectDataKey, String newBusinessObjectDataStatus, String oldBusinessObjectDataStatus)
{
/*
* Need to clear the security context here since the current thread may have been reused, which may might have left over its security context. If we do
* not clear the security context, any subsequent calls may be restricted by the permissions given to the previous thread's security context.
*/
SecurityContextHolder.clearContext();
processBusinessObjectDataNotificationEventSync(notificationEventType, businessObjectDataKey, newBusinessObjectDataStatus, oldBusinessObjectDataStatus);
// 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);
}
@Override
public List<Object> processBusinessObjectDataNotificationEventSync(NotificationEventTypeEntity.EventTypesBdata notificationEventType,
BusinessObjectDataKey businessObjectDataKey, String newBusinessObjectDataStatus, String oldBusinessObjectDataStatus)
{
// Retrieve all matching business object data notification registrations with enabled status.
List<BusinessObjectDataNotificationRegistrationEntity> businessObjectDataNotificationRegistrationEntities =
businessObjectDataNotificationRegistrationDao
.getBusinessObjectDataNotificationRegistrations(notificationEventType.name(), businessObjectDataKey, newBusinessObjectDataStatus,
oldBusinessObjectDataStatus, NotificationRegistrationStatusEntity.ENABLED);
BusinessObjectDataEntity businessObjectDataEntity = businessObjectDataDao.getBusinessObjectDataByAltKey(businessObjectDataKey);
List<BusinessObjectDataNotificationRegistrationEntity> notificationRegistrationsToProcess = new ArrayList<>();
for (BusinessObjectDataNotificationRegistrationEntity notificationRegistration : businessObjectDataNotificationRegistrationEntities)
{
if (notificationRegistration.getStorage() == null)
{
notificationRegistrationsToProcess.add(notificationRegistration);
}
else
{
String filterStorageName = notificationRegistration.getStorage().getName();
for (StorageUnitEntity storageUnitEntity : businessObjectDataEntity.getStorageUnits())
{
if (filterStorageName.equalsIgnoreCase(storageUnitEntity.getStorage().getName()))
{
notificationRegistrationsToProcess.add(notificationRegistration);
break;
}
}
}
}
return processBusinessObjectDataNotifications(notificationEventType.name(), notificationRegistrationsToProcess,
businessObjectDataHelper.createBusinessObjectDataFromEntity(businessObjectDataEntity), newBusinessObjectDataStatus, oldBusinessObjectDataStatus);
}
@Override
@Async
public Future<Void> processStorageUnitNotificationEventAsync(NotificationEventTypeEntity.EventTypesStorageUnit notificationEventType,
BusinessObjectDataKey businessObjectDataKey, String storageName, String newStorageUnitStatus, String oldStorageUnitStatus)
{
/*
* Need to clear the security context here since the current thread may have been reused, which may might have left over its security context. If we do
* not clear the security context, any subsequent calls may be restricted by the permissions given to the previous thread's security context.
*/
SecurityContextHolder.clearContext();
processStorageUnitNotificationEventSync(notificationEventType, businessObjectDataKey, storageName, newStorageUnitStatus, oldStorageUnitStatus);
// 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);
}
@Override
public List<Object> processStorageUnitNotificationEventSync(NotificationEventTypeEntity.EventTypesStorageUnit notificationEventType,
BusinessObjectDataKey businessObjectDataKey, String storageName, String newStorageUnitStatus, String oldStorageUnitStatus)
{
// Retrieve all matching storage unit notification registrations with enabled status.
List<StorageUnitNotificationRegistrationEntity> storageUnitNotificationRegistrationEntities = storageUnitNotificationRegistrationDao
.getStorageUnitNotificationRegistrations(notificationEventType.name(), businessObjectDataKey, storageName, newStorageUnitStatus,
oldStorageUnitStatus, NotificationRegistrationStatusEntity.ENABLED);
BusinessObjectDataEntity businessObjectDataEntity = businessObjectDataDao.getBusinessObjectDataByAltKey(businessObjectDataKey);
return processStorageUnitNotifications(notificationEventType.name(), storageUnitNotificationRegistrationEntities,
businessObjectDataHelper.createBusinessObjectDataFromEntity(businessObjectDataEntity), storageName, newStorageUnitStatus, oldStorageUnitStatus);
}
private List<Object> processBusinessObjectDataNotifications(String notificationEventType,
List<BusinessObjectDataNotificationRegistrationEntity> businessObjectDataNotificationRegistrationEntities, BusinessObjectData businessObjectData,
String newBusinessObjectDataStatus, String oldBusinessObjectDataStatus)
{
List<Object> notificationActions = new ArrayList<>();
// Build a list of partition value that includes primary and sub-partition values, if any are specified in the business object data key.
List<String> partitionValues = businessObjectDataHelper.getPrimaryAndSubPartitionValues(businessObjectData);
// Get a list of partition columns from the associated business object format.
List<String> partitionColumnNames = null;
BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDao.getBusinessObjectFormatByAltKey(
new BusinessObjectFormatKey(businessObjectData.getNamespace(), businessObjectData.getBusinessObjectDefinitionName(),
businessObjectData.getBusinessObjectFormatUsage(), businessObjectData.getBusinessObjectFormatFileType(),
businessObjectData.getBusinessObjectFormatVersion()));
if (businessObjectFormatEntity != null)
{
// Get business object format model object to directly access schema columns and partitions.
BusinessObjectFormat businessObjectFormat = businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity);
// Proceed only if this format has schema with partition columns specified.
if (businessObjectFormat.getSchema() != null && !CollectionUtils.isEmpty(businessObjectFormat.getSchema().getPartitions()))
{
// Do not provide more partition column names than there are primary and
// sub-partition values that this business object data is registered with.
partitionColumnNames = new ArrayList<>();
List<SchemaColumn> partitionColumns = businessObjectFormat.getSchema().getPartitions();
for (int i = 0; i < Math.min(partitionValues.size(), partitionColumns.size()); i++)
{
partitionColumnNames.add(partitionColumns.get(i).getName());
}
}
}
for (BusinessObjectDataNotificationRegistrationEntity businessObjectDataNotificationRegistration : businessObjectDataNotificationRegistrationEntities)
{
// Retrieve the job notification actions needed to be triggered.
for (NotificationActionEntity notificationActionEntity : businessObjectDataNotificationRegistration.getNotificationActions())
{
// Trigger the job action.
if (notificationActionEntity instanceof NotificationJobActionEntity)
{
NotificationJobActionEntity notificationJobActionEntity = (NotificationJobActionEntity) notificationActionEntity;
BusinessObjectDataNotificationEventParamsDto notificationEventParams = new BusinessObjectDataNotificationEventParamsDto();
notificationEventParams.setBusinessObjectDataNotificationRegistration(businessObjectDataNotificationRegistration);
notificationEventParams.setNotificationJobAction(notificationJobActionEntity);
notificationEventParams.setEventType(notificationEventType);
notificationEventParams.setBusinessObjectData(businessObjectData);
notificationEventParams.setPartitionColumnNames(partitionColumnNames);
notificationEventParams.setStorageName(businessObjectDataNotificationRegistration.getStorage() == null ? null :
businessObjectDataNotificationRegistration.getStorage().getName());
notificationEventParams.setPartitionValues(partitionValues);
notificationEventParams.setNewBusinessObjectDataStatus(newBusinessObjectDataStatus);
notificationEventParams.setOldBusinessObjectDataStatus(oldBusinessObjectDataStatus);
notificationActions
.add(triggerNotificationAction(NotificationTypeEntity.NOTIFICATION_TYPE_BDATA, notificationEventType, notificationEventParams));
}
}
}
return notificationActions;
}
private List<Object> processStorageUnitNotifications(String notificationEventType,
List<StorageUnitNotificationRegistrationEntity> storageUnitNotificationRegistrationEntities, BusinessObjectData businessObjectData, String storageName,
String newStorageUnitStatus, String oldStorageUnitStatus)
{
List<Object> notificationActions = new ArrayList<>();
// Build a list of partition value that includes primary and sub-partition values, if any are specified in the business object data key.
List<String> partitionValues = businessObjectDataHelper.getPrimaryAndSubPartitionValues(businessObjectData);
// Get a list of partition columns from the associated business object format.
List<String> partitionColumnNames = null;
BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDao.getBusinessObjectFormatByAltKey(
new BusinessObjectFormatKey(businessObjectData.getNamespace(), businessObjectData.getBusinessObjectDefinitionName(),
businessObjectData.getBusinessObjectFormatUsage(), businessObjectData.getBusinessObjectFormatFileType(),
businessObjectData.getBusinessObjectFormatVersion()));
if (businessObjectFormatEntity != null)
{
// Get business object format model object to directly access schema columns and partitions.
BusinessObjectFormat businessObjectFormat = businessObjectFormatHelper.createBusinessObjectFormatFromEntity(businessObjectFormatEntity);
// Proceed only if this format has schema with partition columns specified.
if (businessObjectFormat.getSchema() != null && !CollectionUtils.isEmpty(businessObjectFormat.getSchema().getPartitions()))
{
// Do not provide more partition column names than there are primary and
// sub-partition values that this business object data is registered with.
partitionColumnNames = new ArrayList<>();
List<SchemaColumn> partitionColumns = businessObjectFormat.getSchema().getPartitions();
for (int i = 0; i < Math.min(partitionValues.size(), partitionColumns.size()); i++)
{
partitionColumnNames.add(partitionColumns.get(i).getName());
}
}
}
for (StorageUnitNotificationRegistrationEntity storageUnitNotificationRegistration : storageUnitNotificationRegistrationEntities)
{
// Retrieve the job notification actions needed to be triggered.
for (NotificationActionEntity notificationActionEntity : storageUnitNotificationRegistration.getNotificationActions())
{
// Trigger the job action.
if (notificationActionEntity instanceof NotificationJobActionEntity)
{
NotificationJobActionEntity notificationJobActionEntity = (NotificationJobActionEntity) notificationActionEntity;
StorageUnitNotificationEventParamsDto notificationEventParams = new StorageUnitNotificationEventParamsDto();
notificationEventParams.setStorageUnitNotificationRegistration(storageUnitNotificationRegistration);
notificationEventParams.setNotificationJobAction(notificationJobActionEntity);
notificationEventParams.setEventType(notificationEventType);
notificationEventParams.setBusinessObjectData(businessObjectData);
notificationEventParams.setPartitionColumnNames(partitionColumnNames);
notificationEventParams.setStorageName(storageName);
notificationEventParams.setPartitionValues(partitionValues);
notificationEventParams.setNewStorageUnitStatus(newStorageUnitStatus);
notificationEventParams.setOldStorageUnitStatus(oldStorageUnitStatus);
notificationActions
.add(triggerNotificationAction(NotificationTypeEntity.NOTIFICATION_TYPE_STORAGE_UNIT, notificationEventType, notificationEventParams));
}
}
}
return notificationActions;
}
private Object triggerNotificationAction(String notificationType, String actionType, NotificationEventParamsDto params)
{
NotificationActionService actionHandler = notificationActionFactory.getNotificationActionHandler(notificationType, actionType);
try
{
return actionHandler.performNotificationAction(params);
}
catch (Exception e)
{
// Log the error.
LOGGER.error("Unexpected error occurred when triggering notification action with " +
actionHandler.getIdentifyingInformation(params, businessObjectDataHelper), e);
}
return null;
}
}