/******************************************************************************* * Copyright 2017 [email protected] * * 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.iotp.infomgt.dao.relation; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.annotation.PostConstruct; import org.iotp.infomgt.dao.model.ModelConstants; import org.iotp.infomgt.dao.model.type.RelationTypeGroupCodec; import org.iotp.infomgt.dao.nosql.CassandraAbstractAsyncDao; import org.iotp.infomgt.dao.nosql.CassandraAbstractSearchTimeDao; import org.iotp.infomgt.dao.util.NoSqlDao; import org.iotp.infomgt.data.common.ThingType; import org.iotp.infomgt.data.id.EntityId; import org.iotp.infomgt.data.id.EntityIdFactory; import org.iotp.infomgt.data.page.TimePageLink; import org.iotp.infomgt.data.relation.EntityRelation; import org.iotp.infomgt.data.relation.RelationTypeGroup; import org.springframework.stereotype.Component; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.Row; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; /** */ @Component @Slf4j @NoSqlDao public class BaseRelationDao extends CassandraAbstractAsyncDao implements RelationDao { private static final String SELECT_COLUMNS = "SELECT " + ModelConstants.RELATION_FROM_ID_PROPERTY + "," + ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," + ModelConstants.RELATION_TO_ID_PROPERTY + "," + ModelConstants.RELATION_TO_TYPE_PROPERTY + "," + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + "," + ModelConstants.RELATION_TYPE_PROPERTY + "," + ModelConstants.ADDITIONAL_INFO_PROPERTY; public static final String FROM = " FROM "; public static final String WHERE = " WHERE "; public static final String AND = " AND "; private static final RelationTypeGroupCodec relationTypeGroupCodec = new RelationTypeGroupCodec(); private PreparedStatement saveStmt; private PreparedStatement findAllByFromStmt; private PreparedStatement findAllByFromAndTypeStmt; private PreparedStatement findAllByToStmt; private PreparedStatement findAllByToAndTypeStmt; private PreparedStatement checkRelationStmt; private PreparedStatement deleteStmt; private PreparedStatement deleteAllByEntityStmt; @PostConstruct public void init() { super.startExecutor(); } @Override public ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from, RelationTypeGroup typeGroup) { BoundStatement stmt = getFindAllByFromStmt().bind() .setUUID(0, from.getId()) .setString(1, from.getEntityType().name()) .set(2, typeGroup, relationTypeGroupCodec); return executeAsyncRead(from, stmt); } @Override public ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) { BoundStatement stmt = getFindAllByFromAndTypeStmt().bind() .setUUID(0, from.getId()) .setString(1, from.getEntityType().name()) .set(2, typeGroup, relationTypeGroupCodec) .setString(3, relationType); return executeAsyncRead(from, stmt); } @Override public ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to, RelationTypeGroup typeGroup) { BoundStatement stmt = getFindAllByToStmt().bind() .setUUID(0, to.getId()) .setString(1, to.getEntityType().name()) .set(2, typeGroup, relationTypeGroupCodec); return executeAsyncRead(to, stmt); } @Override public ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) { BoundStatement stmt = getFindAllByToAndTypeStmt().bind() .setUUID(0, to.getId()) .setString(1, to.getEntityType().name()) .set(2, typeGroup, relationTypeGroupCodec) .setString(3, relationType); return executeAsyncRead(to, stmt); } @Override public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { BoundStatement stmt = getCheckRelationStmt().bind() .setUUID(0, from.getId()) .setString(1, from.getEntityType().name()) .setUUID(2, to.getId()) .setString(3, to.getEntityType().name()) .set(4, typeGroup, relationTypeGroupCodec) .setString(5, relationType); return getFuture(executeAsyncRead(stmt), rs -> rs != null ? rs.one() != null : false); } @Override public ListenableFuture<EntityRelation> getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { BoundStatement stmt = getCheckRelationStmt().bind() .setUUID(0, from.getId()) .setString(1, from.getEntityType().name()) .setUUID(2, to.getId()) .setString(3, to.getEntityType().name()) .set(4, typeGroup, relationTypeGroupCodec) .setString(5, relationType); return getFuture(executeAsyncRead(stmt), rs -> rs != null ? getEntityRelation(rs.one()) : null); } @Override public ListenableFuture<Boolean> saveRelation(EntityRelation relation) { BoundStatement stmt = getSaveStmt().bind() .setUUID(0, relation.getFrom().getId()) .setString(1, relation.getFrom().getEntityType().name()) .setUUID(2, relation.getTo().getId()) .setString(3, relation.getTo().getEntityType().name()) .set(4, relation.getTypeGroup(), relationTypeGroupCodec) .setString(5, relation.getType()) .set(6, relation.getAdditionalInfo(), JsonNode.class); ResultSetFuture future = executeAsyncWrite(stmt); return getBooleanListenableFuture(future); } @Override public ListenableFuture<Boolean> deleteRelation(EntityRelation relation) { return deleteRelation(relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); } @Override public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { BoundStatement stmt = getDeleteStmt().bind() .setUUID(0, from.getId()) .setString(1, from.getEntityType().name()) .setUUID(2, to.getId()) .setString(3, to.getEntityType().name()) .set(4, typeGroup, relationTypeGroupCodec) .setString(5, relationType); ResultSetFuture future = executeAsyncWrite(stmt); return getBooleanListenableFuture(future); } @Override public ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity) { BoundStatement stmt = getDeleteAllByEntityStmt().bind() .setUUID(0, entity.getId()) .setString(1, entity.getEntityType().name()); ResultSetFuture future = executeAsyncWrite(stmt); return getBooleanListenableFuture(future); } @Override public ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, RelationTypeGroup typeGroup, ThingType childType, TimePageLink pageLink) { Select.Where query = CassandraAbstractSearchTimeDao.buildQuery(ModelConstants.RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME, Arrays.asList(eq(ModelConstants.RELATION_FROM_ID_PROPERTY, from.getId()), eq(ModelConstants.RELATION_FROM_TYPE_PROPERTY, from.getEntityType().name()), eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()), eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType), eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())), Arrays.asList( pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY) : QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY), pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_PROPERTY) : QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY), pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TO_TYPE_PROPERTY) : QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY) ), pageLink, ModelConstants.RELATION_TO_ID_PROPERTY); return getFuture(executeAsyncRead(query), this::getEntityRelations); } private PreparedStatement getSaveStmt() { if (saveStmt == null) { saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + "(" + ModelConstants.RELATION_FROM_ID_PROPERTY + "," + ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," + ModelConstants.RELATION_TO_ID_PROPERTY + "," + ModelConstants.RELATION_TO_TYPE_PROPERTY + "," + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + "," + ModelConstants.RELATION_TYPE_PROPERTY + "," + ModelConstants.ADDITIONAL_INFO_PROPERTY + ")" + " VALUES(?, ?, ?, ?, ?, ?, ?)"); } return saveStmt; } private PreparedStatement getDeleteStmt() { if (deleteStmt == null) { deleteStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?" + AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ?" + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ?" + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ?" + AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ?"); } return deleteStmt; } private PreparedStatement getDeleteAllByEntityStmt() { if (deleteAllByEntityStmt == null) { deleteAllByEntityStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?"); } return deleteAllByEntityStmt; } private PreparedStatement getFindAllByFromStmt() { if (findAllByFromStmt == null) { findAllByFromStmt = getSession().prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? "); } return findAllByFromStmt; } private PreparedStatement getFindAllByFromAndTypeStmt() { if (findAllByFromAndTypeStmt == null) { findAllByFromAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? "); } return findAllByFromAndTypeStmt; } private PreparedStatement getFindAllByToStmt() { if (findAllByToStmt == null) { findAllByToStmt = getSession().prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? "); } return findAllByToStmt; } private PreparedStatement getFindAllByToAndTypeStmt() { if (findAllByToAndTypeStmt == null) { findAllByToAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? "); } return findAllByToAndTypeStmt; } private PreparedStatement getCheckRelationStmt() { if (checkRelationStmt == null) { checkRelationStmt = getSession().prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " + AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? "); } return checkRelationStmt; } private EntityId toEntity(Row row, String uuidColumn, String typeColumn) { return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn)); } private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) { log.debug("Generated query [{}] for entity {}", stmt, from); return getFuture(executeAsyncRead(stmt), rs -> getEntityRelations(rs)); } private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) { return getFuture(rsFuture, rs -> rs != null ? rs.wasApplied() : false); } private List<EntityRelation> getEntityRelations(ResultSet rs) { List<Row> rows = rs.all(); List<EntityRelation> entries = new ArrayList<>(rows.size()); if (!rows.isEmpty()) { rows.forEach(row -> { entries.add(getEntityRelation(row)); }); } return entries; } private EntityRelation getEntityRelation(Row row) { EntityRelation relation = new EntityRelation(); relation.setTypeGroup(row.get(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, relationTypeGroupCodec)); relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY)); relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class)); relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY)); relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY)); return relation; } }