feat(driver,manager): broadcast driver metadata events and let drivers refresh on demand

Extends the existing point/device metadata refresh flow to drivers:

- MetadataEvent gains an optional targetServices set so manager-side
  publishers can scope a metadata event to specific driver services
  instead of broadcasting to every listener.
- New DRIVER value on MetadataTypeEnum, plus a new GetById RPC on
  driver_driver.proto so drivers can re-fetch their registered metadata
  without going through the full registration handshake again.
- DriverClient.refreshMetadata(driverId) calls the new RPC and reapplies
  the returned device ids and attribute maps via a shared applyMetadata
  helper.
- MetadataReceiver now handles MetadataTypeEnum.DRIVER:
    DELETE  -> clear DriverMetadata, DeviceMetadata, and PointMetadata
              caches and flip status to OFFLINE
    ADD/UPDATE -> driverClient.refreshMetadata(id)
- Manager-side MetadataEventListener forwards events to RabbitMQ keyed by
  the target services in the event, falling back to broadcast when none
  are specified.
- DeviceServiceImpl, DriverAttributeServiceImpl, PointAttributeServiceImpl,
  and PointServiceImpl populate targetServices using the relevant driver
  service names so changes only wake the drivers that own the affected
  entity.
- DriverDriverServer.getById exposes the new RPC; new DriverDriverServerTest
  covers it.
- MetadataReceiverTest gains driverUpdate / driverDelete cases;
  MetadataEventListenerTest covers the per-service routing.
This commit is contained in:
pnoker
2026-05-21 15:02:09 +08:00
parent 91eef8cbcf
commit e38c7d432a
18 changed files with 589 additions and 32 deletions
@@ -22,6 +22,7 @@ package api.common.driver;
import "api/common/r.proto";
import "api/common/entity.proto";
import "api/common/driver/driver_entity.proto";
import "api/common/driver/driver_query.proto";
// Configuration options for code generation
option java_package = "io.github.pnoker.api.common.driver";
@@ -33,6 +34,9 @@ option java_multiple_files = true;
service DriverApi {
// Driver registration, used by the driver service to register itself with the platform
rpc DriverRegister (GrpcDriverRegisterDTO) returns (GrpcRDriverRegisterDTO);
// Query the current registered driver metadata without re-registering it
rpc GetById (GrpcDriverQuery) returns (GrpcRDriverRegisterDTO);
}
// Driver registration response structure
@@ -51,4 +55,4 @@ message GrpcRDriverRegisterDTO {
// List of point attribute configurations supported by the driver
repeated GrpcPointAttributeDTO point_attributes = 5;
}
}
@@ -44,6 +44,11 @@ public enum MetadataTypeEnum {
* Point metadata
*/
POINT((byte) 1, "point", "Point metadata"),
/**
* Driver metadata
*/
DRIVER((byte) 2, "driver", "Driver metadata"),
;
/**
@@ -60,6 +60,12 @@ public class MetadataEventListener implements ApplicationListener<MetadataEvent>
entityEvent.setMetadataType(MetadataTypeEnum.POINT);
entityEvent.setOperateType(metadataEvent.getOperateType());
driverCustomService.event(entityEvent);
} else if (MetadataTypeEnum.DRIVER.equals(metadataType)) {
MetadataEventDTO entityEvent = new MetadataEventDTO();
entityEvent.setId(metadataEvent.getId());
entityEvent.setMetadataType(MetadataTypeEnum.DRIVER);
entityEvent.setOperateType(metadataEvent.getOperateType());
driverCustomService.event(entityEvent);
}
}
@@ -22,6 +22,7 @@ import io.github.pnoker.api.common.GrpcDriverDTO;
import io.github.pnoker.api.common.GrpcPointAttributeDTO;
import io.github.pnoker.api.common.driver.DriverApiGrpc;
import io.github.pnoker.api.common.driver.GrpcDriverRegisterDTO;
import io.github.pnoker.api.common.driver.GrpcDriverQuery;
import io.github.pnoker.api.common.driver.GrpcRDriverRegisterDTO;
import io.github.pnoker.common.driver.entity.bo.DriverBO;
import io.github.pnoker.common.driver.entity.bo.RegisterBO;
@@ -33,6 +34,7 @@ import io.github.pnoker.common.driver.entity.dto.PointAttributeDTO;
import io.github.pnoker.common.driver.metadata.DriverMetadata;
import io.github.pnoker.common.enums.DriverStatusEnum;
import io.github.pnoker.common.exception.RegisterException;
import io.github.pnoker.common.exception.ServiceException;
import io.github.pnoker.common.optional.CollectionOptional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -41,6 +43,7 @@ import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -98,6 +101,30 @@ public class DriverClient {
throw new RegisterException(rDriverRegisterDTO.getResult().getMessage());
}
applyMetadata(rDriverRegisterDTO);
}
/**
* Reloads the current driver metadata from manager without submitting registration
* properties again.
*
* @param driverId registered driver id
*/
public void refreshMetadata(Long driverId) {
if (Objects.isNull(driverId) || driverId <= 0) {
throw new ServiceException("Failed to refresh driver metadata: invalid driver id");
}
GrpcDriverQuery query = GrpcDriverQuery.newBuilder().setDriverId(driverId).build();
GrpcRDriverRegisterDTO rDriverRegisterDTO = driverApiBlockingStub.getById(query);
if (!rDriverRegisterDTO.getResult().getOk()) {
throw new ServiceException(rDriverRegisterDTO.getResult().getMessage());
}
applyMetadata(rDriverRegisterDTO);
}
private void applyMetadata(GrpcRDriverRegisterDTO rDriverRegisterDTO) {
DriverBO driverBO = driverBuilder.buildDTOByGrpcDTO(rDriverRegisterDTO.getDriver());
driverMetadata.setDriver(driverBO);
@@ -116,8 +116,18 @@ public final class DeviceMetadata {
* @param id device identifier
*/
public void loadCache(long id) {
CompletableFuture<DeviceBO> future = CompletableFuture.supplyAsync(() -> deviceClient.getById(id));
cache.put(id, future);
CompletableFuture.supplyAsync(() -> deviceClient.getById(id))
.whenComplete((device, throwable) -> {
if (Objects.nonNull(throwable)) {
log.error("Failed to reload device metadata, deviceId={}", id, throwable);
return;
}
if (Objects.isNull(device)) {
cache.synchronous().invalidate(id);
return;
}
cache.put(id, CompletableFuture.completedFuture(device));
});
}
/**
@@ -126,7 +136,14 @@ public final class DeviceMetadata {
* @param id device identifier
*/
public void removeCache(long id) {
cache.put(id, CompletableFuture.completedFuture(null));
cache.synchronous().invalidate(id);
}
/**
* Clears all cached device metadata.
*/
public void clearCache() {
cache.synchronous().invalidateAll();
}
/**
@@ -76,12 +76,12 @@ public final class DriverMetadata {
* Current driver status.
*/
@Setter
private DriverStatusEnum driverStatus = DriverStatusEnum.OFFLINE;
private volatile DriverStatusEnum driverStatus = DriverStatusEnum.OFFLINE;
/**
* Registered driver definition.
*/
@Setter
private DriverBO driver;
private volatile DriverBO driver;
private static <E> void replaceContents(Set<E> target, Set<E> source) {
target.clear();
@@ -117,4 +117,14 @@ public final class DriverMetadata {
replaceContents(this.pointAttributeNameMap, pointAttributeNameMap);
}
public void clear() {
deviceIds.clear();
driverAttributeIdMap.clear();
driverAttributeNameMap.clear();
pointAttributeIdMap.clear();
pointAttributeNameMap.clear();
driver = null;
driverStatus = DriverStatusEnum.OFFLINE;
}
}
@@ -103,8 +103,18 @@ public final class PointMetadata {
* @param id point identifier
*/
public void loadCache(long id) {
CompletableFuture<PointBO> future = CompletableFuture.supplyAsync(() -> pointClient.getById(id));
cache.put(id, future);
CompletableFuture.supplyAsync(() -> pointClient.getById(id))
.whenComplete((point, throwable) -> {
if (throwable != null) {
log.error("Failed to reload point metadata, pointId={}", id, throwable);
return;
}
if (point == null) {
cache.synchronous().invalidate(id);
return;
}
cache.put(id, CompletableFuture.completedFuture(point));
});
}
/**
@@ -113,7 +123,14 @@ public final class PointMetadata {
* @param id point identifier
*/
public void removeCache(long id) {
cache.put(id, CompletableFuture.completedFuture(null));
cache.synchronous().invalidate(id);
}
/**
* Clears all cached point metadata.
*/
public void clearCache() {
cache.synchronous().invalidateAll();
}
}
@@ -19,6 +19,7 @@ package io.github.pnoker.common.driver.receiver.rabbit;
import com.rabbitmq.client.Channel;
import io.github.pnoker.common.driver.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.driver.grpc.client.DriverClient;
import io.github.pnoker.common.driver.metadata.DeviceMetadata;
import io.github.pnoker.common.driver.metadata.DriverMetadata;
import io.github.pnoker.common.driver.metadata.PointMetadata;
@@ -56,6 +57,8 @@ public class MetadataReceiver {
private final DeviceMetadata deviceMetadata;
private final DriverClient driverClient;
private final MetadataEventPublisher metadataEventPublisher;
/**
@@ -112,6 +115,20 @@ public class MetadataReceiver {
// Publish point metadata event
metadataEventPublisher.publishEvent(
new MetadataEvent(this, entityDTO.getId(), MetadataTypeEnum.POINT, entityDTO.getOperateType()));
} else if (MetadataTypeEnum.DRIVER.equals(entityDTO.getMetadataType())) {
if (MetadataOperateTypeEnum.DELETE.equals(entityDTO.getOperateType())) {
log.info("Delete driver metadata: {}", entityDTO.getId());
driverMetadata.clear();
deviceMetadata.clearCache();
pointMetadata.clearCache();
} else if (MetadataOperateTypeEnum.ADD.equals(entityDTO.getOperateType())
|| MetadataOperateTypeEnum.UPDATE.equals(entityDTO.getOperateType())) {
log.info("Refresh driver metadata: {}", entityDTO.getId());
driverClient.refreshMetadata(entityDTO.getId());
}
metadataEventPublisher.publishEvent(
new MetadataEvent(this, entityDTO.getId(), MetadataTypeEnum.DRIVER, entityDTO.getOperateType()));
} else {
log.error("Unsupported metadata type: {}", entityDTO.getMetadataType());
RabbitAckUtil.reject(channel, deliveryTag);
@@ -18,6 +18,7 @@
package io.github.pnoker.common.driver.receiver.rabbit;
import com.rabbitmq.client.Channel;
import io.github.pnoker.common.driver.entity.bo.DriverBO;
import io.github.pnoker.common.driver.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.driver.grpc.client.DriverClient;
import io.github.pnoker.common.driver.metadata.DeviceMetadata;
@@ -25,6 +26,7 @@ import io.github.pnoker.common.driver.metadata.DriverMetadata;
import io.github.pnoker.common.driver.metadata.PointMetadata;
import io.github.pnoker.common.entity.dto.MetadataEventDTO;
import io.github.pnoker.common.entity.event.MetadataEvent;
import io.github.pnoker.common.enums.DriverStatusEnum;
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
import io.github.pnoker.common.enums.MetadataTypeEnum;
import org.junit.jupiter.api.BeforeEach;
@@ -151,6 +153,33 @@ class MetadataReceiverTest {
verify(channel).basicAck(eq(7L), eq(false));
}
@Test
void driverUpdateRefreshesDriverMetadata() throws Exception {
MetadataEventDTO dto = event(MetadataTypeEnum.DRIVER, MetadataOperateTypeEnum.UPDATE, 7L);
receiver.metadataReceive(channel, message, dto);
verify(driverClient).refreshMetadata(7L);
verify(metadataEventPublisher).publishEvent(org.mockito.ArgumentMatchers.any(MetadataEvent.class));
verify(channel).basicAck(eq(7L), eq(false));
}
@Test
void driverDeleteClearsAllDriverSideCaches() throws Exception {
driverMetadata.setDriver(new DriverBO());
driverMetadata.setDriverStatus(DriverStatusEnum.ONLINE);
MetadataEventDTO dto = event(MetadataTypeEnum.DRIVER, MetadataOperateTypeEnum.DELETE, 7L);
receiver.metadataReceive(channel, message, dto);
verify(deviceMetadata).clearCache();
verify(pointMetadata).clearCache();
assertThat(driverMetadata.getDeviceIds()).isEmpty();
assertThat(driverMetadata.getDriver()).isNull();
assertThat(driverMetadata.getDriverStatus()).isEqualTo(DriverStatusEnum.OFFLINE);
verify(driverClient, never()).refreshMetadata(7L);
verify(metadataEventPublisher).publishEvent(org.mockito.ArgumentMatchers.any(MetadataEvent.class));
verify(channel).basicAck(eq(7L), eq(false));
}
@Test
void nacksAndRequeuesOnPublisherFailure() throws Exception {
MetadataEventDTO dto = event(MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.ADD, 10L);
@@ -26,12 +26,15 @@ import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.utils.JsonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
import java.util.Objects;
/**
* Event listener that processes metadata change events.
@@ -43,26 +46,40 @@ import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class MetadataEventListener implements ApplicationListener<MetadataEvent> {
public class MetadataEventListener {
private final DriverService driverService;
private final RabbitTemplate rabbitTemplate;
@Async
@Override
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void onApplicationEvent(MetadataEvent metadataEvent) {
log.info("Metadata event listener received: {}", JsonUtil.toJsonString(metadataEvent));
try {
Long id = metadataEvent.getId();
MetadataTypeEnum metadataType = metadataEvent.getMetadataType();
MetadataEventDTO entityDTO = new MetadataEventDTO(id, metadataType, metadataEvent.getOperateType());
if (CollectionUtils.isNotEmpty(metadataEvent.getTargetServices())) {
metadataEvent.getTargetServices().forEach(service -> notifyDriver(service, entityDTO));
return;
}
if (MetadataTypeEnum.DEVICE.equals(metadataType)) {
DriverBO entityBO = driverService.listByDeviceId(id);
notifyDriver(entityBO.getServiceName(), entityDTO);
if (Objects.nonNull(entityBO)) {
notifyDriver(entityBO.getServiceName(), entityDTO);
}
} else if (MetadataTypeEnum.POINT.equals(metadataType)) {
List<DriverBO> entityBOList = driverService.selectByPointId(id);
entityBOList.forEach(entityBO -> notifyDriver(entityBO.getServiceName(), entityDTO));
if (CollectionUtils.isNotEmpty(entityBOList)) {
entityBOList.forEach(entityBO -> notifyDriver(entityBO.getServiceName(), entityDTO));
}
} else if (MetadataTypeEnum.DRIVER.equals(metadataType)) {
DriverBO entityBO = driverService.getById(id);
if (Objects.nonNull(entityBO)) {
notifyDriver(entityBO.getServiceName(), entityDTO);
}
}
} catch (Exception e) {
log.error("Metadata event listener failed, event={}", JsonUtil.toJsonString(metadataEvent), e);
@@ -74,6 +91,9 @@ public class MetadataEventListener implements ApplicationListener<MetadataEvent>
* @param entityDTO DriverTransferMetadataDTO
*/
private void notifyDriver(String service, MetadataEventDTO entityDTO) {
if (Objects.isNull(service) || service.isBlank()) {
return;
}
log.info("Notify driver[{}]: {}", service, JsonUtil.toJsonString(entityDTO));
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE_METADATA,
RabbitConstant.ROUTING_DRIVER_METADATA_PREFIX + service, entityDTO);
@@ -23,6 +23,7 @@ import io.github.pnoker.api.common.GrpcPointAttributeDTO;
import io.github.pnoker.api.common.GrpcR;
import io.github.pnoker.api.common.driver.DriverApiGrpc;
import io.github.pnoker.api.common.driver.GrpcDriverRegisterDTO;
import io.github.pnoker.api.common.driver.GrpcDriverQuery;
import io.github.pnoker.api.common.driver.GrpcRDriverRegisterDTO;
import io.github.pnoker.common.enums.ResponseEnum;
import io.github.pnoker.common.manager.biz.DriverRegisterService;
@@ -33,12 +34,17 @@ import io.github.pnoker.common.manager.grpc.builder.GrpcDriverAttributeBuilder;
import io.github.pnoker.common.manager.grpc.builder.GrpcDriverBuilder;
import io.github.pnoker.common.manager.grpc.builder.GrpcPointAttributeBuilder;
import io.github.pnoker.common.manager.service.DeviceService;
import io.github.pnoker.common.manager.service.DriverAttributeService;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointAttributeService;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* gRPC server handling driver-to-manager driver requests.
@@ -60,6 +66,12 @@ public class DriverDriverServer extends DriverApiGrpc.DriverApiImplBase {
private final DriverRegisterService driverRegisterService;
private final DriverService driverService;
private final DriverAttributeService driverAttributeService;
private final PointAttributeService pointAttributeService;
private final DeviceService deviceService;
@Override
@@ -110,4 +122,60 @@ public class DriverDriverServer extends DriverApiGrpc.DriverApiImplBase {
responseObserver.onCompleted();
}
@Override
public void getById(GrpcDriverQuery request, StreamObserver<GrpcRDriverRegisterDTO> responseObserver) {
GrpcRDriverRegisterDTO.Builder builder = GrpcRDriverRegisterDTO.newBuilder();
GrpcR.Builder rBuilder = GrpcR.newBuilder();
try {
DriverBO entityBO = driverService.getById(request.getDriverId());
if (Objects.isNull(entityBO)) {
rBuilder.setOk(false);
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
rBuilder.setMessage(ResponseEnum.NO_RESOURCE.getText());
} else {
buildMetadataResponse(builder, entityBO);
rBuilder.setOk(true);
rBuilder.setCode(ResponseEnum.OK.getCode());
rBuilder.setMessage(ResponseEnum.OK.getText());
}
} catch (Exception e) {
rBuilder.setOk(false);
rBuilder.setCode(ResponseEnum.FAILURE.getCode());
rBuilder.setMessage(e.getMessage());
log.error("Driver metadata gRPC query failed, driverId={}", request.getDriverId(), e);
}
builder.setResult(rBuilder);
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
private void buildMetadataResponse(GrpcRDriverRegisterDTO.Builder builder, DriverBO entityBO) {
builder.setDriver(grpcDriverBuilder.buildGrpcDTOByBO(entityBO));
List<GrpcDriverAttributeDTO> driverAttributeDTOList = Optional
.ofNullable(driverAttributeService.listByDriverId(entityBO.getId()))
.orElseGet(List::of)
.stream()
.filter(attribute -> Objects.equals(entityBO.getTenantId(), attribute.getTenantId()))
.map(grpcDriverAttributeBuilder::buildGrpcDTOByBO)
.toList();
builder.addAllDriverAttributes(driverAttributeDTOList);
List<GrpcPointAttributeDTO> pointAttributeDTOList = Optional
.ofNullable(pointAttributeService.listByDriverId(entityBO.getId()))
.orElseGet(List::of)
.stream()
.filter(attribute -> Objects.equals(entityBO.getTenantId(), attribute.getTenantId()))
.map(grpcPointAttributeBuilder::buildGrpcDTOByBO)
.toList();
builder.addAllPointAttributes(pointAttributeDTOList);
List<Long> idList = Optional.ofNullable(deviceService.listIdsByDriverId(entityBO.getId())).orElseGet(List::of);
builder.addAllDeviceIds(idList);
}
}
@@ -122,6 +122,7 @@ public class DeviceServiceImpl implements DeviceService {
private final MetadataEventPublisher metadataEventPublisher;
@Override
@Transactional
public void add(DeviceBO entityBO) {
validateTenantRelations(entityBO);
@@ -139,7 +140,7 @@ public class DeviceServiceImpl implements DeviceService {
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.ADD);
MetadataOperateTypeEnum.ADD, driverServiceNames(entityDO.getDriverId()));
metadataEventPublisher.publishEvent(metadataEvent);
}
@@ -147,6 +148,7 @@ public class DeviceServiceImpl implements DeviceService {
@Transactional
public void delete(Long id) {
DeviceDO entityDO = getDOById(id, true);
Set<String> targetServices = driverServiceNames(entityDO.getDriverId());
//
profileBindService.removeByDeviceId(id);
@@ -157,13 +159,15 @@ public class DeviceServiceImpl implements DeviceService {
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.DELETE);
MetadataOperateTypeEnum.DELETE, targetServices);
metadataEventPublisher.publishEvent(metadataEvent);
}
@Override
@Transactional
public void update(DeviceBO entityBO) {
DeviceDO entityDO = getDOById(entityBO.getId(), true);
Long oldDriverId = entityDO.getDriverId();
if (!Objects.equals(entityBO.getTenantId(), entityDO.getTenantId())) {
throw new NotFoundException("Resource does not exist");
}
@@ -198,9 +202,16 @@ public class DeviceServiceImpl implements DeviceService {
entityBO.setDeviceName(deviceBO.getDeviceName());
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.UPDATE);
metadataEventPublisher.publishEvent(metadataEvent);
if (Objects.equals(oldDriverId, entityBO.getDriverId())) {
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.UPDATE, driverServiceNames(entityBO.getDriverId()));
metadataEventPublisher.publishEvent(metadataEvent);
} else {
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.DELETE, driverServiceNames(oldDriverId)));
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.ADD, driverServiceNames(entityBO.getDriverId())));
}
}
@Override
@@ -610,6 +621,24 @@ public class DeviceServiceImpl implements DeviceService {
return !isUpdate || !one.getId().equals(entityBO.getId());
}
private Set<String> driverServiceNames(Long... driverIds) {
Set<String> services = new HashSet<>();
if (Objects.isNull(driverIds)) {
return services;
}
for (Long driverId : driverIds) {
if (Objects.isNull(driverId)) {
continue;
}
DriverBO driverBO = driverService.getById(driverId);
if (Objects.nonNull(driverBO) && StringUtils.isNotBlank(driverBO.getServiceName())) {
services.add(driverBO.getServiceName());
}
}
return services;
}
/**
* Primary key ID
*
@@ -23,6 +23,9 @@ import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapp
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.pnoker.common.constant.common.QueryWrapperConstant;
import io.github.pnoker.common.entity.common.Pages;
import io.github.pnoker.common.entity.event.MetadataEvent;
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
import io.github.pnoker.common.enums.MetadataTypeEnum;
import io.github.pnoker.common.exception.AddException;
import io.github.pnoker.common.exception.DeleteException;
import io.github.pnoker.common.exception.DuplicateException;
@@ -34,6 +37,7 @@ import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.builder.DriverAttributeBuilder;
import io.github.pnoker.common.manager.entity.model.DriverAttributeDO;
import io.github.pnoker.common.manager.entity.query.DriverAttributeQuery;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.service.DriverAttributeService;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.utils.FieldUtil;
@@ -46,6 +50,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Business service implementation for driver attribute operations.
@@ -65,6 +70,8 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
private final DriverService driverService;
private final MetadataEventPublisher metadataEventPublisher;
@Override
public void add(DriverAttributeBO entityBO) {
validateTenantRelations(entityBO);
@@ -76,15 +83,17 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
if (!driverAttributeManager.save(entityDO)) {
throw new AddException("Failed to create driver attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
public void delete(Long id) {
getDOById(id, true);
DriverAttributeDO entityDO = getDOById(id, true);
if (!driverAttributeManager.removeById(id)) {
throw new DeleteException("Failed to remove driver attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
@@ -104,6 +113,7 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
if (!driverAttributeManager.updateById(entityDO)) {
throw new UpdateException("Failed to update driver attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
@@ -140,6 +150,7 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
if (!driverAttributeManager.saveBatch(doList)) {
throw new AddException("Failed to batch create driver attributes");
}
entityBOList.stream().map(DriverAttributeBO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -156,6 +167,7 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
if (!driverAttributeManager.updateBatchById(doList)) {
throw new UpdateException("Failed to batch update driver attributes");
}
entityBOList.stream().map(DriverAttributeBO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -163,9 +175,11 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
if (Objects.isNull(ids) || ids.isEmpty()) {
return;
}
List<DriverAttributeDO> entityDOList = driverAttributeManager.listByIds(ids);
if (!driverAttributeManager.removeByIds(ids)) {
throw new DeleteException("Failed to batch remove driver attributes");
}
entityDOList.stream().map(DriverAttributeDO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -226,6 +240,15 @@ public class DriverAttributeServiceImpl implements DriverAttributeService {
}
}
private void publishDriverMetadataEvent(Long driverId) {
DriverBO driverBO = driverService.getById(driverId);
if (Objects.isNull(driverBO) || StringUtils.isBlank(driverBO.getServiceName())) {
return;
}
metadataEventPublisher.publishEvent(new MetadataEvent(this, driverId, MetadataTypeEnum.DRIVER,
MetadataOperateTypeEnum.UPDATE, Set.of(driverBO.getServiceName())));
}
/**
* Primary key ID
*
@@ -23,6 +23,9 @@ import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapp
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.pnoker.common.constant.common.QueryWrapperConstant;
import io.github.pnoker.common.entity.common.Pages;
import io.github.pnoker.common.entity.event.MetadataEvent;
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
import io.github.pnoker.common.enums.MetadataTypeEnum;
import io.github.pnoker.common.exception.AddException;
import io.github.pnoker.common.exception.DeleteException;
import io.github.pnoker.common.exception.DuplicateException;
@@ -34,6 +37,7 @@ import io.github.pnoker.common.manager.entity.bo.PointAttributeBO;
import io.github.pnoker.common.manager.entity.builder.PointAttributeBuilder;
import io.github.pnoker.common.manager.entity.model.PointAttributeDO;
import io.github.pnoker.common.manager.entity.query.PointAttributeQuery;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointAttributeService;
import io.github.pnoker.common.utils.FieldUtil;
@@ -46,6 +50,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Business service implementation for point attribute operations.
@@ -65,6 +70,8 @@ public class PointAttributeServiceImpl implements PointAttributeService {
private final DriverService driverService;
private final MetadataEventPublisher metadataEventPublisher;
@Override
public void add(PointAttributeBO entityBO) {
validateTenantRelations(entityBO);
@@ -74,15 +81,17 @@ public class PointAttributeServiceImpl implements PointAttributeService {
if (!pointAttributeManager.save(entityDO)) {
throw new AddException("Failed to create point attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
public void delete(Long id) {
getDOById(id, true);
PointAttributeDO entityDO = getDOById(id, true);
if (!pointAttributeManager.removeById(id)) {
throw new DeleteException("Failed to remove point attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
@@ -100,6 +109,7 @@ public class PointAttributeServiceImpl implements PointAttributeService {
if (!pointAttributeManager.updateById(entityDO)) {
throw new UpdateException("Failed to update point attribute");
}
publishDriverMetadataEvent(entityDO.getDriverId());
}
@Override
@@ -136,6 +146,7 @@ public class PointAttributeServiceImpl implements PointAttributeService {
if (!pointAttributeManager.saveBatch(doList)) {
throw new AddException("Failed to batch create point attributes");
}
entityBOList.stream().map(PointAttributeBO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -152,6 +163,7 @@ public class PointAttributeServiceImpl implements PointAttributeService {
if (!pointAttributeManager.updateBatchById(doList)) {
throw new UpdateException("Failed to batch update point attributes");
}
entityBOList.stream().map(PointAttributeBO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -159,9 +171,11 @@ public class PointAttributeServiceImpl implements PointAttributeService {
if (Objects.isNull(ids) || ids.isEmpty()) {
return;
}
List<PointAttributeDO> entityDOList = pointAttributeManager.listByIds(ids);
if (!pointAttributeManager.removeByIds(ids)) {
throw new DeleteException("Failed to batch remove point attributes");
}
entityDOList.stream().map(PointAttributeDO::getDriverId).distinct().forEach(this::publishDriverMetadataEvent);
}
@Override
@@ -227,6 +241,15 @@ public class PointAttributeServiceImpl implements PointAttributeService {
}
}
private void publishDriverMetadataEvent(Long driverId) {
DriverBO driverBO = driverService.getById(driverId);
if (Objects.isNull(driverBO) || StringUtils.isBlank(driverBO.getServiceName())) {
return;
}
metadataEventPublisher.publishEvent(new MetadataEvent(this, driverId, MetadataTypeEnum.DRIVER,
MetadataOperateTypeEnum.UPDATE, Set.of(driverBO.getServiceName())));
}
/**
* Primary key ID
*
@@ -37,6 +37,7 @@ import io.github.pnoker.common.manager.dal.PointAttributeConfigManager;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.DeviceByPointBO;
import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.bo.PointBO;
import io.github.pnoker.common.manager.entity.bo.PointConfigByDeviceBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
@@ -49,6 +50,7 @@ import io.github.pnoker.common.manager.entity.query.PointQuery;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.mapper.DeviceMapper;
import io.github.pnoker.common.manager.mapper.PointMapper;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.manager.service.ProfileService;
@@ -59,7 +61,9 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -98,7 +102,10 @@ public class PointServiceImpl implements PointService {
private final DeviceMapper deviceMapper;
private final DriverService driverService;
@Override
@Transactional
public void add(PointBO entityBO) {
validateTenantRelations(entityBO);
checkDuplicate(entityBO, false, true);
@@ -109,16 +116,19 @@ public class PointServiceImpl implements PointService {
}
//
metadataEventPublisher.publishEvent(
new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT, MetadataOperateTypeEnum.ADD));
List<Long> deviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
deviceIds.forEach(entityId -> metadataEventPublisher
.publishEvent(new MetadataEvent(this, entityId, MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.UPDATE)));
metadataEventPublisher.publishEvent(
new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT, MetadataOperateTypeEnum.ADD,
driverServiceNamesByDeviceIds(deviceIds)));
publishDeviceUpdateEvents(deviceIds);
}
@Override
@Transactional
public void delete(Long id) {
PointDO entityDO = getDOById(id, true);
List<Long> deviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
Set<String> targetServices = driverServiceNamesByDeviceIds(deviceIds);
if (!pointManager.removeById(id)) {
throw new DeleteException("Failed to remove ");
@@ -126,16 +136,16 @@ public class PointServiceImpl implements PointService {
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.DELETE);
MetadataOperateTypeEnum.DELETE, targetServices);
metadataEventPublisher.publishEvent(metadataEvent);
List<Long> deviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
deviceIds.forEach(entityId -> metadataEventPublisher
.publishEvent(new MetadataEvent(this, entityId, MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.UPDATE)));
publishDeviceUpdateEvents(deviceIds);
}
@Override
@Transactional
public void update(PointBO entityBO) {
PointDO current = getDOById(entityBO.getId(), true);
List<Long> oldDeviceIds = profileBindService.listDeviceIdsByProfileId(current.getProfileId());
if (!Objects.equals(entityBO.getTenantId(), current.getTenantId())) {
throw new NotFoundException("Resource does not exist");
}
@@ -150,9 +160,19 @@ public class PointServiceImpl implements PointService {
}
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.UPDATE);
metadataEventPublisher.publishEvent(metadataEvent);
if (Objects.equals(current.getProfileId(), entityDO.getProfileId())) {
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.UPDATE, driverServiceNamesByDeviceIds(oldDeviceIds));
metadataEventPublisher.publishEvent(metadataEvent);
} else {
List<Long> newDeviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.DELETE, driverServiceNamesByDeviceIds(oldDeviceIds)));
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.ADD, driverServiceNamesByDeviceIds(newDeviceIds)));
publishDeviceUpdateEvents(oldDeviceIds);
publishDeviceUpdateEvents(newDeviceIds);
}
}
@Override
@@ -370,6 +390,37 @@ public class PointServiceImpl implements PointService {
}
}
private void publishDeviceUpdateEvents(Collection<Long> deviceIds) {
if (CollectionUtils.isEmpty(deviceIds)) {
return;
}
deviceIds.forEach(deviceId -> metadataEventPublisher.publishEvent(
new MetadataEvent(this, deviceId, MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.UPDATE,
driverServiceNamesByDeviceId(deviceId))));
}
private Set<String> driverServiceNamesByDeviceIds(Collection<Long> deviceIds) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptySet();
}
return deviceIds.stream()
.map(this::driverServiceNamesByDeviceId)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
private Set<String> driverServiceNamesByDeviceId(Long deviceId) {
if (Objects.isNull(deviceId)) {
return Collections.emptySet();
}
DriverBO driverBO = driverService.listByDeviceId(deviceId);
if (Objects.isNull(driverBO) || StringUtils.isBlank(driverBO.getServiceName())) {
return Collections.emptySet();
}
return Set.of(driverBO.getServiceName());
}
/**
* Primary key ID
*
@@ -33,6 +33,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -107,6 +108,31 @@ class MetadataEventListenerTest {
verifyNoInteractions(rabbitTemplate);
}
@Test
void eventWithTargetServicesBypassesOwnerLookup() {
listener.onApplicationEvent(new MetadataEvent(this, 10L, MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.DELETE, Set.of("dc3-driver-old")));
verify(driverService, never()).listByDeviceId(10L);
verify(rabbitTemplate).convertAndSend(
eq(RabbitConstant.TOPIC_EXCHANGE_METADATA),
eq(RabbitConstant.ROUTING_DRIVER_METADATA_PREFIX + "dc3-driver-old"),
any(MetadataEventDTO.class));
}
@Test
void driverEventNotifiesRegisteredDriverService() {
when(driverService.getById(7L)).thenReturn(driver);
listener.onApplicationEvent(new MetadataEvent(this, 7L, MetadataTypeEnum.DRIVER,
MetadataOperateTypeEnum.UPDATE));
verify(rabbitTemplate).convertAndSend(
eq(RabbitConstant.TOPIC_EXCHANGE_METADATA),
eq(RabbitConstant.ROUTING_DRIVER_METADATA_PREFIX + "dc3-driver-modbus-tcp"),
any(MetadataEventDTO.class));
}
@Test
void serviceFailureIsSwallowedSilently() {
when(driverService.listByDeviceId(10L)).thenThrow(new RuntimeException("downstream offline"));
@@ -0,0 +1,158 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.grpc.server.driver;
import io.github.pnoker.api.common.GrpcDriverAttributeDTO;
import io.github.pnoker.api.common.GrpcDriverDTO;
import io.github.pnoker.api.common.GrpcPointAttributeDTO;
import io.github.pnoker.api.common.driver.DriverApiGrpc;
import io.github.pnoker.api.common.driver.GrpcDriverQuery;
import io.github.pnoker.api.common.driver.GrpcRDriverRegisterDTO;
import io.github.pnoker.common.enums.ResponseEnum;
import io.github.pnoker.common.manager.biz.DriverRegisterService;
import io.github.pnoker.common.manager.entity.bo.DriverAttributeBO;
import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.bo.PointAttributeBO;
import io.github.pnoker.common.manager.grpc.builder.GrpcDriverAttributeBuilder;
import io.github.pnoker.common.manager.grpc.builder.GrpcDriverBuilder;
import io.github.pnoker.common.manager.grpc.builder.GrpcPointAttributeBuilder;
import io.github.pnoker.common.manager.service.DeviceService;
import io.github.pnoker.common.manager.service.DriverAttributeService;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointAttributeService;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DriverDriverServerTest {
@Mock
private GrpcDriverBuilder grpcDriverBuilder;
@Mock
private GrpcDriverAttributeBuilder grpcDriverAttributeBuilder;
@Mock
private GrpcPointAttributeBuilder grpcPointAttributeBuilder;
@Mock
private DriverRegisterService driverRegisterService;
@Mock
private DriverService driverService;
@Mock
private DriverAttributeService driverAttributeService;
@Mock
private PointAttributeService pointAttributeService;
@Mock
private DeviceService deviceService;
private Server server;
private ManagedChannel channel;
private DriverApiGrpc.DriverApiBlockingStub stub;
@BeforeEach
void setUp() throws Exception {
DriverDriverServer driverServer = new DriverDriverServer(grpcDriverBuilder, grpcDriverAttributeBuilder,
grpcPointAttributeBuilder, driverRegisterService, driverService, driverAttributeService,
pointAttributeService, deviceService);
String name = "dc3-driver-metadata-" + UUID.randomUUID();
server = InProcessServerBuilder.forName(name).directExecutor().addService(driverServer).build().start();
channel = InProcessChannelBuilder.forName(name).directExecutor().build();
stub = DriverApiGrpc.newBlockingStub(channel);
}
@AfterEach
void tearDown() {
if (channel != null) {
channel.shutdownNow();
}
if (server != null) {
server.shutdownNow();
}
}
@Test
void getByIdReturnsRegisteredMetadataSnapshot() {
DriverBO driver = new DriverBO();
driver.setId(7L);
driver.setTenantId(100L);
DriverAttributeBO driverAttribute = new DriverAttributeBO();
driverAttribute.setTenantId(100L);
PointAttributeBO pointAttribute = new PointAttributeBO();
pointAttribute.setTenantId(100L);
when(driverService.getById(7L)).thenReturn(driver);
when(grpcDriverBuilder.buildGrpcDTOByBO(driver)).thenReturn(GrpcDriverDTO.newBuilder().build());
when(driverAttributeService.listByDriverId(7L)).thenReturn(List.of(driverAttribute));
when(pointAttributeService.listByDriverId(7L)).thenReturn(List.of(pointAttribute));
when(grpcDriverAttributeBuilder.buildGrpcDTOByBO(driverAttribute))
.thenReturn(GrpcDriverAttributeDTO.newBuilder().build());
when(grpcPointAttributeBuilder.buildGrpcDTOByBO(pointAttribute))
.thenReturn(GrpcPointAttributeDTO.newBuilder().build());
when(deviceService.listIdsByDriverId(7L)).thenReturn(List.of(1L, 2L));
GrpcRDriverRegisterDTO response = stub.getById(GrpcDriverQuery.newBuilder().setDriverId(7L).build());
assertThat(response.getResult().getOk()).isTrue();
assertThat(response.getResult().getCode()).isEqualTo(ResponseEnum.OK.getCode());
assertThat(response.getDriverAttributesCount()).isEqualTo(1);
assertThat(response.getPointAttributesCount()).isEqualTo(1);
assertThat(response.getDeviceIdsList()).containsExactly(1L, 2L);
}
@Test
void getByIdReturnsNoResourceWhenDriverMissing() {
when(driverService.getById(404L)).thenReturn(null);
GrpcRDriverRegisterDTO response = stub.getById(GrpcDriverQuery.newBuilder().setDriverId(404L).build());
assertThat(response.getResult().getOk()).isFalse();
assertThat(response.getResult().getCode()).isEqualTo(ResponseEnum.NO_RESOURCE.getCode());
}
@Test
void getByIdReturnsFailureWhenLookupThrows() {
when(driverService.getById(7L)).thenThrow(new IllegalStateException("metadata unavailable"));
GrpcRDriverRegisterDTO response = stub.getById(GrpcDriverQuery.newBuilder().setDriverId(7L).build());
assertThat(response.getResult().getOk()).isFalse();
assertThat(response.getResult().getCode()).isEqualTo(ResponseEnum.FAILURE.getCode());
assertThat(response.getResult().getMessage()).isEqualTo("metadata unavailable");
}
}
@@ -22,6 +22,12 @@ import io.github.pnoker.common.enums.MetadataTypeEnum;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Metadata event.
*
@@ -38,6 +44,8 @@ public class MetadataEvent extends ApplicationEvent {
private final MetadataOperateTypeEnum operateType;
private final Set<String> targetServices;
/**
* Constructor.
*
@@ -47,10 +55,29 @@ public class MetadataEvent extends ApplicationEvent {
* @param operateType Metadata operation type
*/
public MetadataEvent(Object source, Long id, MetadataTypeEnum metadataType, MetadataOperateTypeEnum operateType) {
this(source, id, metadataType, operateType, Collections.emptySet());
}
/**
* Constructor.
*
* @param source Event source object
* @param id Metadata ID
* @param metadataType Metadata type
* @param operateType Metadata operation type
* @param targetServices Driver services that must receive this event
*/
public MetadataEvent(Object source, Long id, MetadataTypeEnum metadataType, MetadataOperateTypeEnum operateType,
Collection<String> targetServices) {
super(source);
this.id = id;
this.metadataType = metadataType;
this.operateType = operateType;
this.targetServices = Objects.isNull(targetServices) ? Collections.emptySet()
: targetServices.stream()
.filter(Objects::nonNull)
.filter(service -> !service.isBlank())
.collect(Collectors.toUnmodifiableSet());
}
}