/* * Copyright 2014-2016 CyberVision, Inc. * * 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.kaaproject.kaa.server.common.dao.service; import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.kaaproject.kaa.server.common.dao.impl.DaoUtil.convertDtoList; import static org.kaaproject.kaa.server.common.dao.impl.DaoUtil.getDto; import static org.kaaproject.kaa.server.common.dao.service.Validator.isValidId; import static org.kaaproject.kaa.server.common.dao.service.Validator.validateHash; import static org.kaaproject.kaa.server.common.dao.service.Validator.validateId; import static org.kaaproject.kaa.server.common.dao.service.Validator.validateObject; import static org.kaaproject.kaa.server.common.dao.service.Validator.validateSqlId; import org.apache.avro.generic.GenericRecord; import org.apache.commons.lang.StringUtils; import org.kaaproject.kaa.common.avro.GenericAvroConverter; import org.kaaproject.kaa.common.dto.EndpointNotificationDto; import org.kaaproject.kaa.common.dto.NotificationDto; import org.kaaproject.kaa.common.dto.NotificationSchemaDto; import org.kaaproject.kaa.common.dto.NotificationTypeDto; import org.kaaproject.kaa.common.dto.UpdateNotificationDto; import org.kaaproject.kaa.common.dto.VersionDto; import org.kaaproject.kaa.server.common.dao.NotificationService; import org.kaaproject.kaa.server.common.dao.exception.DatabaseProcessingException; import org.kaaproject.kaa.server.common.dao.exception.IncorrectParameterException; import org.kaaproject.kaa.server.common.dao.impl.EndpointNotificationDao; import org.kaaproject.kaa.server.common.dao.impl.EndpointProfileDao; import org.kaaproject.kaa.server.common.dao.impl.NotificationDao; import org.kaaproject.kaa.server.common.dao.impl.NotificationSchemaDao; import org.kaaproject.kaa.server.common.dao.impl.TopicDao; import org.kaaproject.kaa.server.common.dao.model.EndpointNotification; import org.kaaproject.kaa.server.common.dao.model.EndpointProfile; import org.kaaproject.kaa.server.common.dao.model.Notification; import org.kaaproject.kaa.server.common.dao.model.sql.NotificationSchema; import org.kaaproject.kaa.server.common.dao.model.sql.Topic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.TimeZone; @Service @Transactional public class NotificationServiceImpl implements NotificationService { private static final Logger LOG = LoggerFactory.getLogger(NotificationServiceImpl.class); @Value("#{sql_dao[dao_max_wait_time]}") private int waitSeconds; @Autowired(required = false) private int ttl = 7 * 24 * 3600 * 1000; @Autowired private TopicDao<Topic> topicDao; @Autowired private NotificationSchemaDao<NotificationSchema> notificationSchemaDao; private EndpointProfileDao<EndpointProfile> endpointProfileDao; private NotificationDao<Notification> notificationDao; private EndpointNotificationDao<EndpointNotification> unicastNotificationDao; @Override public NotificationSchemaDto saveNotificationSchema(NotificationSchemaDto notificationSchemaDto) { validateNotificationSchemaObject(notificationSchemaDto); String id = notificationSchemaDto.getId(); if (StringUtils.isBlank(id)) { notificationSchemaDto.setId(null); notificationSchemaDto.setCreatedTime(System.currentTimeMillis()); NotificationSchema foundSchema; NotificationTypeDto type = notificationSchemaDto.getType(); if (type != null) { foundSchema = notificationSchemaDao.findLatestNotificationSchemaByAppId( notificationSchemaDto.getApplicationId(), type); } else { throw new IncorrectParameterException( "Invalid Notification type in Notification Schema object."); } if (foundSchema != null) { int lastSchemaVersion = foundSchema.getVersion(); notificationSchemaDto.setVersion(++lastSchemaVersion); } else { notificationSchemaDto.incrementVersion(); } } else { NotificationSchemaDto oldNotificationSchemaDto = getDto(notificationSchemaDao.findById(id)); if (oldNotificationSchemaDto != null) { oldNotificationSchemaDto.editFields(notificationSchemaDto); notificationSchemaDto = oldNotificationSchemaDto; } else { LOG.error("Can't find notification schema with given id [{}].", id); throw new IncorrectParameterException("Invalid notification schema id: " + id); } } return getDto(notificationSchemaDao.save(new NotificationSchema(notificationSchemaDto))); } @Override public UpdateNotificationDto<NotificationDto> saveNotification(NotificationDto dto) { validateObject(dto, "Can't save notification. Invalid notification object"); dto.setId(null); UpdateNotificationDto<NotificationDto> updateNotificationDto = null; String schemaId = dto.getSchemaId(); String topicId = dto.getTopicId(); if (isNotBlank(schemaId) && isNotBlank(topicId)) { NotificationSchema schema = notificationSchemaDao.findById(schemaId); if (schema != null) { dto.setNfVersion(schema.getVersion()); dto.setApplicationId(schema.getApplicationId()); dto.setType(schema.getType()); } else { throw new DatabaseProcessingException("Can't find notification schema by id " + schemaId); } try { dto.setBody(serializeNotificationBody(dto, schema)); } catch (IOException ex) { LOG.error("Can't serialize notification body using schema. ", ex); throw new DatabaseProcessingException("Can't serialize notification body using schema: " + schemaId); } long currentTime = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTimeInMillis(); Date expiredAt = dto.getExpiredAt(); dto.setExpiredAt(expiredAt != null ? expiredAt : new Date(currentTime + ttl)); dto.setLastTimeModify(new Date(currentTime)); NotificationDto notificationDto = saveNotificationAndIncTopicSecNum(dto); if (notificationDto != null) { updateNotificationDto = new UpdateNotificationDto<NotificationDto>(); updateNotificationDto.setAppId(notificationDto.getApplicationId()); updateNotificationDto.setTopicId(topicId); updateNotificationDto.setPayload(notificationDto); } return updateNotificationDto; } else { throw new IncorrectParameterException( "Incorrect notification object notification schema id is empty"); } } /** * Sends a notification and increments a topic sequence number. * * @param dto notification * @return saved notification */ public NotificationDto saveNotificationAndIncTopicSecNum(NotificationDto dto) { NotificationDto notificationDto = null; Topic topic = topicDao.getNextSeqNumber(dto.getTopicId()); if (topic != null) { dto.setSecNum(topic.getSequenceNumber()); Notification savedDto = notificationDao.save(dto); notificationDto = savedDto != null ? savedDto.toDto() : null; } else { LOG.warn("Can't find topic by id."); } return notificationDto; } private byte[] serializeNotificationBody(NotificationDto nf, NotificationSchema nfSchema) throws IOException { GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>(nfSchema.getCtlSchema().getBody()); String notificationJson = new String(nf.getBody(), Charset.forName("UTF8")); GenericRecord notificationAvro = converter.decodeJson(notificationJson); return converter.encode(notificationAvro); } @Override public NotificationDto findNotificationById(String id) { NotificationDto dto = null; LOG.debug("Find notification by id [{}] ", id); if (StringUtils.isNotBlank(id)) { dto = getDto(notificationDao.findById(id)); } LOG.trace("Found notification object {} by id [{}] ", dto, id); return dto; } @Override public List<NotificationDto> findNotificationsByTopicId(String topicId) { validateId(topicId, "Can't find notifications. Invalid topic id: " + topicId); return convertDtoList(notificationDao.findNotificationsByTopicId(topicId)); } @Override public NotificationSchemaDto findNotificationSchemaById(String id) { validateId(id, "Can't find notification schema. Invalid notification schema id: " + id); return getDto(notificationSchemaDao.findById(id)); } @Override public List<NotificationSchemaDto> findNotificationSchemasByAppId(String appId) { validateId(appId, "Can't find notification schemas. Invalid application id: " + appId); return convertDtoList(notificationSchemaDao.findNotificationSchemasByAppId(appId)); } @Override public List<VersionDto> findUserNotificationSchemasByAppId(String applicationId) { validateId(applicationId, "Can't find schemas. Invalid application id: " + applicationId); List<NotificationSchema> notificationSchemas = notificationSchemaDao .findNotificationSchemasByAppIdAndType(applicationId, NotificationTypeDto.USER); List<VersionDto> schemas = new ArrayList<>(); for (NotificationSchema notificationSchema : notificationSchemas) { schemas.add(notificationSchema.toVersionDto()); } return schemas; } @Override public List<VersionDto> findNotificationSchemaVersionsByAppId( String applicationId) { validateId(applicationId, "Can't find notification schema versions. Invalid application id: " + applicationId); List<NotificationSchema> notificationSchemas = notificationSchemaDao.findNotificationSchemasByAppId(applicationId); List<VersionDto> schemas = new ArrayList<>(); for (NotificationSchema notificationSchema : notificationSchemas) { schemas.add(notificationSchema.toVersionDto()); } return schemas; } @Override public void removeNotificationSchemasByAppId(String appId) { validateId(appId, "Can't remove notification schemas. Invalid application id: " + appId); LOG.debug("Cascade remove corresponding notification to application id [{}]", appId); unicastNotificationDao.removeNotificationsByAppId(appId); notificationSchemaDao.removeNotificationSchemasByAppId(appId); } @Override public List<NotificationDto> findNotificationsByTopicIdAndVersionAndStartSecNum( String topicId, int seqNum, int sysNfVersion, int userNfVersion) { validateSqlId(topicId, "Can't find notifications. Invalid topic id: " + topicId); return convertDtoList(notificationDao.findNotificationsByTopicIdAndVersionAndStartSecNum( topicId, seqNum, sysNfVersion, userNfVersion)); } @Override public List<NotificationSchemaDto> findNotificationSchemasByAppIdAndType( String appId, NotificationTypeDto type) { validateId(appId, "Can't find notification schemas. Invalid application id: " + appId); return convertDtoList(notificationSchemaDao.findNotificationSchemasByAppIdAndType(appId, type)); } @Override public NotificationSchemaDto findNotificationSchemaByAppIdAndTypeAndVersion( String appId, NotificationTypeDto type, int majorVersion) { validateId(appId, "Can't find notification schema. Invalid application id: " + appId); return getDto(notificationSchemaDao.findNotificationSchemasByAppIdAndTypeAndVersion( appId, type, majorVersion)); } @Override public EndpointNotificationDto findUnicastNotificationById(String id) { validateId(id, "Can't find unicast notification. Invalid id " + id); return getDto(unicastNotificationDao.findById(id)); } @Override public UpdateNotificationDto<EndpointNotificationDto> saveUnicastNotification( EndpointNotificationDto dto) { validateObject(dto, "Can't save unicast notification. Invalid endpoint notification object."); UpdateNotificationDto<EndpointNotificationDto> updateNotificationDto = null; NotificationDto notificationDto = dto.getNotificationDto(); String schemaId = notificationDto.getSchemaId(); String topicId = notificationDto.getTopicId(); if (isBlank(schemaId)) { throw new IncorrectParameterException("Invalid notification schema id: " + schemaId); } else if (isBlank(topicId)) { throw new IncorrectParameterException("Invalid notification topic id: " + schemaId); } else { byte[] endpointKeyHash = dto.getEndpointKeyHash(); if (endpointKeyHash != null) { EndpointProfile ep = endpointProfileDao.findByKeyHash(endpointKeyHash); if (ep == null) { throw new DatabaseProcessingException("Can't find endpoint profile by hash " + endpointKeyHash); } if (ep.getSubscriptions() == null || !ep.getSubscriptions().contains(topicId)) { //TODO Error code? throw new DatabaseProcessingException("Endpoint profile is not subscribed to this topic"); } } else { throw new IncorrectParameterException("Invalid endpointKeyHash: " + endpointKeyHash); } notificationDto.setId(null); notificationDto.setTopicId(topicId); notificationDto.setSecNum(-1); NotificationSchema schema = notificationSchemaDao.findById(schemaId); if (schema != null) { notificationDto.setNfVersion(schema.getVersion()); notificationDto.setApplicationId(schema.getApplicationId()); notificationDto.setType(schema.getType()); try { notificationDto.setBody(serializeNotificationBody(notificationDto, schema)); } catch (IOException ex) { LOG.error("Can't serialize notification body using schema. ", ex); throw new DatabaseProcessingException("Can't serialize notification body using schema: " + schemaId); } } else { throw new DatabaseProcessingException("Can't find notification schema by id " + schemaId); } long currentTime = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTimeInMillis(); Date expiredAt = notificationDto.getExpiredAt(); notificationDto.setExpiredAt(expiredAt != null ? expiredAt : new Date(currentTime + ttl)); notificationDto.setLastTimeModify(new Date(currentTime)); EndpointNotificationDto unicast = getDto(unicastNotificationDao.save(dto)); if (unicast != null && unicast.getNotificationDto() != null) { LOG.trace("Saved unicast notifications {}", unicast); updateNotificationDto = new UpdateNotificationDto<EndpointNotificationDto>(); NotificationDto savedDto = unicast.getNotificationDto(); updateNotificationDto.setAppId(savedDto.getApplicationId()); updateNotificationDto.setTopicId(savedDto.getTopicId()); updateNotificationDto.setPayload(unicast); } return updateNotificationDto; } } @Override public List<EndpointNotificationDto> findUnicastNotificationsByKeyHash(final byte[] keyHash) { validateHash(keyHash, "Can't find unicast notification. Invalid key hash " + keyHash); return convertDtoList(unicastNotificationDao.findNotificationsByKeyHash(keyHash)); } @Override public void removeUnicastNotificationsByKeyHash(final byte[] keyHash) { validateHash(keyHash, "Can't remove unicast notification. Invalid key hash " + keyHash); unicastNotificationDao.removeNotificationsByKeyHash(keyHash); } @Override public void removeUnicastNotificationById(String id) { validateId(id, "Can't remove unicast notification. Invalid id " + id); unicastNotificationDao.removeById(id); } private void validateNotificationSchemaObject(NotificationSchemaDto dto) { validateObject(dto, "Invalid notification schema object"); if (isBlank(dto.getApplicationId()) && !isValidId(dto.getApplicationId()) || dto.getType() == null) { throw new IncorrectParameterException( "Invalid notification schema object. Check type or applicationId."); } } public void setEndpointProfileDao(EndpointProfileDao<EndpointProfile> endpointProfileDao) { this.endpointProfileDao = endpointProfileDao; } public void setNotificationDao(NotificationDao<Notification> notificationDao) { this.notificationDao = notificationDao; } public void setUnicastNotificationDao( EndpointNotificationDao<EndpointNotification> unicastNotificationDao) { this.unicastNotificationDao = unicastNotificationDao; } }