13 changed files with 445 additions and 45 deletions
@ -0,0 +1,14 @@ |
|||||
|
package org.thingsboard.server.dao.sql; |
||||
|
|
||||
|
import org.thingsboard.server.dao.model.BaseEntity; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 5/6/2017. |
||||
|
*/ |
||||
|
public abstract class JpaAbstractSearchTextDao <E extends BaseEntity<D>, D> extends JpaAbstractDao<E, D> { |
||||
|
|
||||
|
@Override |
||||
|
protected boolean isSearchTextDao() { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
package org.thingsboard.server.dao.sql; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import org.springframework.data.jpa.domain.Specification; |
||||
|
import org.thingsboard.server.common.data.page.TimePageLink; |
||||
|
import org.thingsboard.server.dao.model.BaseEntity; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.*; |
||||
|
|
||||
|
import javax.persistence.criteria.CriteriaBuilder; |
||||
|
import javax.persistence.criteria.CriteriaQuery; |
||||
|
import javax.persistence.criteria.Predicate; |
||||
|
import javax.persistence.criteria.Root; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 5/4/2017. |
||||
|
*/ |
||||
|
public abstract class JpaAbstractSearchTimeDao<E extends BaseEntity<D>, D> extends JpaAbstractDao<E, D> { |
||||
|
|
||||
|
protected Specification<E> getTimeSearchPageSpec(TimePageLink pageLink) { |
||||
|
return new Specification<E>() { |
||||
|
@Override |
||||
|
public Predicate toPredicate(Root<E> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { |
||||
|
Predicate lowerBound = null; |
||||
|
Predicate upperBound = null; |
||||
|
List<Predicate> predicates = new ArrayList<>(); |
||||
|
if (pageLink.isAscOrder()) { |
||||
|
if (pageLink.getIdOffset() != null) { |
||||
|
lowerBound = criteriaBuilder.greaterThan(root.get(ID_PROPERTY), pageLink.getIdOffset()); |
||||
|
predicates.add(lowerBound); |
||||
|
} else if (pageLink.getStartTime() != null) { |
||||
|
UUID startOf = UUIDs.startOf(pageLink.getStartTime()); |
||||
|
lowerBound = criteriaBuilder.greaterThanOrEqualTo(root.get(ID_PROPERTY), startOf); |
||||
|
predicates.add(lowerBound); |
||||
|
} |
||||
|
if (pageLink.getEndTime() != null) { |
||||
|
UUID endOf = UUIDs.endOf(pageLink.getEndTime()); |
||||
|
upperBound = criteriaBuilder.lessThanOrEqualTo(root.get(ID_PROPERTY), endOf); |
||||
|
predicates.add(upperBound); |
||||
|
} |
||||
|
} else { |
||||
|
if (pageLink.getIdOffset() != null) { |
||||
|
lowerBound = criteriaBuilder.lessThan(root.get(ID_PROPERTY), pageLink.getIdOffset()); |
||||
|
predicates.add(lowerBound); |
||||
|
} else if (pageLink.getEndTime() != null) { |
||||
|
UUID endOf = UUIDs.endOf(pageLink.getEndTime()); |
||||
|
lowerBound = criteriaBuilder.lessThanOrEqualTo(root.get(ID_PROPERTY), endOf); |
||||
|
predicates.add(lowerBound); |
||||
|
} |
||||
|
if (pageLink.getStartTime() != null) { |
||||
|
UUID startOf = UUIDs.startOf(pageLink.getStartTime()); |
||||
|
upperBound = criteriaBuilder.greaterThanOrEqualTo(root.get(ID_PROPERTY), startOf); |
||||
|
predicates.add(upperBound); |
||||
|
} |
||||
|
} |
||||
|
return criteriaBuilder.and(predicates.toArray(new Predicate[0])); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
package org.thingsboard.server.dao.sql.event; |
||||
|
|
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
||||
|
import org.springframework.data.jpa.repository.Query; |
||||
|
import org.springframework.data.repository.CrudRepository; |
||||
|
import org.springframework.data.repository.query.Param; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.Event; |
||||
|
import org.thingsboard.server.dao.model.sql.EventEntity; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 5/3/2017. |
||||
|
*/ |
||||
|
@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true", matchIfMissing = false) |
||||
|
public interface EventRepository extends CrudRepository<EventEntity, UUID>, JpaSpecificationExecutor<EventEntity> { |
||||
|
|
||||
|
EventEntity findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid( |
||||
|
UUID tenantId, EntityType entityType, UUID id, String eventType, String eventUid); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,137 @@ |
|||||
|
package org.thingsboard.server.dao.sql.event; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.data.domain.PageRequest; |
||||
|
import org.springframework.data.domain.Pageable; |
||||
|
import org.springframework.data.domain.Sort; |
||||
|
import org.springframework.data.jpa.domain.Specification; |
||||
|
import org.springframework.data.repository.CrudRepository; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.Event; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.page.TimePageLink; |
||||
|
import org.thingsboard.server.dao.DaoUtil; |
||||
|
import org.thingsboard.server.dao.event.EventDao; |
||||
|
import org.thingsboard.server.dao.model.sql.EventEntity; |
||||
|
import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao; |
||||
|
|
||||
|
import javax.persistence.criteria.CriteriaBuilder; |
||||
|
import javax.persistence.criteria.CriteriaQuery; |
||||
|
import javax.persistence.criteria.Predicate; |
||||
|
import javax.persistence.criteria.Root; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.springframework.data.jpa.domain.Specifications.where; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 5/3/2017. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true", matchIfMissing = false) |
||||
|
public class JpaBaseEventDao extends JpaAbstractSearchTimeDao<EventEntity, Event> implements EventDao { |
||||
|
|
||||
|
private final UUID systemTenantId = NULL_UUID; |
||||
|
|
||||
|
@Autowired |
||||
|
private EventRepository eventRepository; |
||||
|
|
||||
|
@Override |
||||
|
protected Class<EventEntity> getEntityClass() { |
||||
|
return EventEntity.class; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected String getColumnFamilyName() { |
||||
|
return EVENT_COLUMN_FAMILY_NAME; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected CrudRepository<EventEntity, UUID> getCrudRepository() { |
||||
|
return eventRepository; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Event save(Event event) { |
||||
|
if (StringUtils.isEmpty(event.getUid())) { |
||||
|
event.setUid(event.getId().toString()); |
||||
|
} |
||||
|
return save(new EventEntity(event), false).orElse(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Optional<Event> saveIfNotExists(Event event) { |
||||
|
return save(new EventEntity(event), true); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Event findEvent(UUID tenantId, EntityId entityId, String eventType, String eventUid) { |
||||
|
return DaoUtil.getData(eventRepository.findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid( |
||||
|
tenantId, entityId.getEntityType(), entityId.getId(), eventType, eventUid)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<Event> findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) { |
||||
|
return findEvents(tenantId, entityId, null, pageLink); |
||||
|
} |
||||
|
@Override |
||||
|
public List<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { |
||||
|
Specification<EventEntity> timeSearchSpec = getTimeSearchPageSpec(pageLink); |
||||
|
Specification<EventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, eventType); |
||||
|
Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; |
||||
|
Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); |
||||
|
return DaoUtil.convertDataList(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); |
||||
|
} |
||||
|
|
||||
|
public Optional<Event> save(EventEntity entity, boolean ifNotExists) { |
||||
|
log.debug("Save event [{}] ", entity); |
||||
|
if (entity.getTenantId() == null) { |
||||
|
log.trace("Save system event with predefined id {}", systemTenantId); |
||||
|
entity.setTenantId(systemTenantId); |
||||
|
} |
||||
|
if (entity.getId() == null) { |
||||
|
entity.setId(UUIDs.timeBased()); |
||||
|
} |
||||
|
if (StringUtils.isEmpty(entity.getEventUid())) { |
||||
|
entity.setEventUid(entity.getId().toString()); |
||||
|
} |
||||
|
if (ifNotExists && findById(entity.getId()) != null) { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
return Optional.of(DaoUtil.getData(eventRepository.save(entity))); |
||||
|
} |
||||
|
|
||||
|
private Specification<EventEntity> getEntityFieldsSpec(UUID tenantId, EntityId entityId, String eventType) { |
||||
|
return new Specification<EventEntity>() { |
||||
|
@Override |
||||
|
public Predicate toPredicate(Root<EventEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { |
||||
|
List<Predicate> predicates = new ArrayList<Predicate>(); |
||||
|
if (tenantId != null) { |
||||
|
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), tenantId); |
||||
|
predicates.add(tenantIdPredicate); |
||||
|
} |
||||
|
if (entityId != null) { |
||||
|
Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType()); |
||||
|
Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), entityId.getId()); |
||||
|
predicates.add(entityTypePredicate); |
||||
|
predicates.add(entityIdPredicate); |
||||
|
} |
||||
|
if (eventType != null) { |
||||
|
Predicate eventTypePredicate = criteriaBuilder.equal(root.get("eventType"), eventType); |
||||
|
predicates.add(eventTypePredicate); |
||||
|
} |
||||
|
return criteriaBuilder.and(predicates.toArray(new Predicate[0])); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
package org.thingsboard.server.dao.sql.event; |
||||
|
|
||||
|
import ch.qos.logback.core.net.SyslogOutputStream; |
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.github.springtestdbunit.annotation.DatabaseSetup; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.thingsboard.server.common.data.Event; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.EventId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.page.TimePageLink; |
||||
|
import org.thingsboard.server.dao.AbstractJpaDaoTest; |
||||
|
import org.thingsboard.server.dao.event.EventDao; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.Date; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.junit.Assert.*; |
||||
|
import static org.thingsboard.server.common.data.DataConstants.STATS; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 5/5/2017. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { |
||||
|
|
||||
|
public static final long HOUR_MILLISECONDS = (long) 3.6e+6; |
||||
|
@Autowired |
||||
|
private EventDao eventDao; |
||||
|
|
||||
|
@Test |
||||
|
@DatabaseSetup("classpath:dbunit/empty_dataset.xml") |
||||
|
public void testSaveIfNotExists() { |
||||
|
UUID eventId = UUIDs.timeBased(); |
||||
|
UUID tenantId = UUIDs.timeBased(); |
||||
|
UUID entityId = UUIDs.timeBased(); |
||||
|
Event event = getEvent(eventId, tenantId, entityId); |
||||
|
Optional<Event> optEvent1 = eventDao.saveIfNotExists(event); |
||||
|
assertTrue("Optional is expected to be non-empty", optEvent1.isPresent()); |
||||
|
assertEquals(optEvent1.get(), event); |
||||
|
Optional<Event> optEvent2 = eventDao.saveIfNotExists(event); |
||||
|
assertFalse("Optional is expected to be empty", optEvent2.isPresent()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
@DatabaseSetup("classpath:dbunit/events.xml") |
||||
|
public void findEvent() { |
||||
|
UUID tenantId = UUID.fromString("be41c7a0-31f5-11e7-9cfd-2786e6aa2046"); |
||||
|
UUID entityId = UUID.fromString("be41c7a1-31f5-11e7-9cfd-2786e6aa2046"); |
||||
|
String eventType = STATS; |
||||
|
String eventUid = "be41c7a3-31f5-11e7-9cfd-2786e6aa2046"; |
||||
|
Event event = eventDao.findEvent(tenantId, new DeviceId(entityId), eventType, eventUid); |
||||
|
assertNotNull("Event expected to be not null", event); |
||||
|
assertEquals("be41c7a2-31f5-11e7-9cfd-2786e6aa2046", event.getId().getId().toString()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
@DatabaseSetup("classpath:dbunit/empty_dataset.xml") |
||||
|
public void findEventsByEntityIdAndPageLink() { |
||||
|
UUID tenantId = UUIDs.timeBased(); |
||||
|
UUID entityId1 = UUIDs.timeBased(); |
||||
|
UUID entityId2 = UUIDs.timeBased(); |
||||
|
long startTime = System.currentTimeMillis(); |
||||
|
long endTime = createEventsTwoEntities(tenantId, entityId1, entityId2, startTime, 20); |
||||
|
List<Event> allEvents = eventDao.find(); |
||||
|
|
||||
|
assertEquals(20, allEvents.size()); |
||||
|
|
||||
|
TimePageLink pageLink1 = new TimePageLink(30, null, null, true); |
||||
|
List<Event> events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink1); |
||||
|
assertEquals(10, events1.size()); |
||||
|
|
||||
|
TimePageLink pageLink2 = new TimePageLink(30, startTime, null, true); |
||||
|
List<Event> events2 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink2); |
||||
|
assertEquals(10, events2.size()); |
||||
|
|
||||
|
TimePageLink pageLink3 = new TimePageLink(30, startTime, endTime, true); |
||||
|
List<Event> events3 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink3); |
||||
|
assertEquals(10, events3.size()); |
||||
|
|
||||
|
TimePageLink pageLink4 = new TimePageLink(5, startTime, endTime, true); |
||||
|
List<Event> events4 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink4); |
||||
|
assertEquals(5, events4.size()); |
||||
|
|
||||
|
UUID idOffset = events4.get(4).getId().getId(); |
||||
|
TimePageLink pageLink5 = new TimePageLink(10, startTime, endTime, true, idOffset); |
||||
|
List<Event> events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink5); |
||||
|
assertEquals(5, events5.size()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private long createEventsTwoEntities(UUID tenantId, UUID entityId1, UUID entityId2, long startTime, int count) { |
||||
|
// Generate #count events for two entities with timestamps from an hour ago till now
|
||||
|
|
||||
|
// Distribute events uniformly
|
||||
|
long step = HOUR_MILLISECONDS / count; |
||||
|
long timestamp = startTime; |
||||
|
for (int i = 0; i < count / 2; i++) { |
||||
|
//UUID eventId1 = UUIDs.startOf(timestamp);
|
||||
|
UUID eventId1 = UUIDs.timeBased(); |
||||
|
Event event1 = getEvent(eventId1, tenantId, entityId1); |
||||
|
eventDao.save(event1); |
||||
|
timestamp += step; |
||||
|
//UUID eventId2 = UUIDs.startOf(timestamp);
|
||||
|
UUID eventId2 = UUIDs.timeBased(); |
||||
|
Event event2 = getEvent(eventId2, tenantId, entityId2); |
||||
|
eventDao.save(event2); |
||||
|
timestamp += step; |
||||
|
} |
||||
|
return System.currentTimeMillis(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
@DatabaseSetup("classpath:dbunit/empty_dataset.xml") |
||||
|
public void findEventsByEntityIdAndEventTypeAndPageLink() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private Event getEvent(UUID eventId, UUID tenantId, UUID entityId) { |
||||
|
Event event = new Event(); |
||||
|
event.setId(new EventId(eventId)); |
||||
|
event.setTenantId(new TenantId(tenantId)); |
||||
|
EntityId deviceId = new DeviceId(entityId); |
||||
|
event.setEntityId(deviceId); |
||||
|
event.setUid(entityId.toString()); |
||||
|
event.setType(STATS); |
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
try { |
||||
|
JsonNode jsonNode = mapper.readTree("{\"key\":\"value\"}"); |
||||
|
event.setBody(jsonNode); |
||||
|
} catch (IOException e) { |
||||
|
log.error(e.getMessage(), e); |
||||
|
} |
||||
|
return event; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<dataset> |
||||
|
<event |
||||
|
id="uuid'be41c7a2-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
tenant_id="uuid'be41c7a0-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
entity_id="uuid'be41c7a1-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
entity_type="1" |
||||
|
event_type="STATS" |
||||
|
event_uid="be41c7a3-31f5-11e7-9cfd-2786e6aa2046" |
||||
|
/> |
||||
|
<event |
||||
|
id="uuid'be41c7a4-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
tenant_id="uuid'be41c7a0-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
entity_id="uuid'be41c7a1-31f5-11e7-9cfd-2786e6aa2046'" |
||||
|
entity_type="1" |
||||
|
event_type="STATS" |
||||
|
event_uid="be41c7a5-31f5-11e7-9cfd-2786e6aa2046" |
||||
|
/> |
||||
|
</dataset> |
||||
Loading…
Reference in new issue