use of io.vertx.core.Promise in project hono by eclipse.
the class HonoConnectionImpl method createReceiver.
@Override
public Future<ProtonReceiver> createReceiver(final String sourceAddress, final ProtonQoS qos, final ProtonMessageHandler messageHandler, final int preFetchSize, final boolean autoAccept, final Handler<String> remoteCloseHook) {
Objects.requireNonNull(sourceAddress);
Objects.requireNonNull(qos);
Objects.requireNonNull(messageHandler);
if (preFetchSize < 0) {
throw new IllegalArgumentException("pre-fetch size must be >= 0");
}
return executeOnContext(result -> {
checkConnected().compose(v -> {
final Promise<ProtonReceiver> receiverPromise = Promise.promise();
final ProtonReceiver receiver = session.createReceiver(sourceAddress);
if (clientConfigProperties.getMaxMessageSize() > ClientConfigProperties.MAX_MESSAGE_SIZE_UNLIMITED) {
receiver.setMaxMessageSize(new UnsignedLong(clientConfigProperties.getMaxMessageSize()));
}
receiver.setAutoAccept(autoAccept);
receiver.setQoS(qos);
receiver.setPrefetch(preFetchSize);
receiver.handler((delivery, message) -> {
HonoProtonHelper.onReceivedMessageDeliveryUpdatedFromRemote(delivery, d -> log.debug("got unexpected disposition update for received message [remote state: {}]", delivery.getRemoteState()));
try {
messageHandler.handle(delivery, message);
if (log.isTraceEnabled()) {
final int remainingCredits = receiver.getCredit() - receiver.getQueued();
log.trace("handling message [remotely settled: {}, queued messages: {}, remaining credit: {}]", delivery.remotelySettled(), receiver.getQueued(), remainingCredits);
}
} catch (final Exception ex) {
log.warn("error handling message", ex);
ProtonHelper.released(delivery, true);
}
});
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening receiver [{}] failed: got disconnected", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
receiver.openHandler(recvOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (receiverPromise.future().isComplete()) {
log.debug("ignoring server response for opening receiver [{}]: receiver creation already timed out", sourceAddress);
} else if (recvOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = receiver.getRemoteCondition();
if (error == null) {
log.debug("opening receiver [{}] failed", sourceAddress, recvOpen.cause());
receiverPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open receiver", recvOpen.cause()));
} else {
log.debug("opening receiver [{}] failed: {} - {}", sourceAddress, error.getCondition(), error.getDescription());
receiverPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(receiver)) {
log.debug("receiver open [source: {}]", sourceAddress);
receiverPromise.tryComplete(recvOpen.result());
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for source [{}] and will detach the link", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(receiver, remoteDetached -> onRemoteDetach(receiver, connection.getRemoteContainer(), false, remoteCloseHook));
HonoProtonHelper.setCloseHandler(receiver, remoteClosed -> onRemoteDetach(receiver, connection.getRemoteContainer(), true, remoteCloseHook));
receiver.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(receiver, clientConfigProperties, receiverPromise);
}
});
return receiverPromise.future();
}).onComplete(result);
});
}
use of io.vertx.core.Promise in project hono by eclipse.
the class GenericSenderLink method sendMessageAndWaitForOutcome.
private Future<ProtonDelivery> sendMessageAndWaitForOutcome(final Message message, final Span currentSpan, final boolean mapUnacceptedOutcomeToErrorResult) {
Objects.requireNonNull(message);
Objects.requireNonNull(currentSpan);
final AtomicReference<ProtonDelivery> deliveryRef = new AtomicReference<>();
final Promise<ProtonDelivery> result = Promise.promise();
final String messageId = String.format("%s-%d", getClass().getSimpleName(), MESSAGE_COUNTER.getAndIncrement());
message.setMessageId(messageId);
logMessageIdAndSenderInfo(currentSpan, messageId);
final SendMessageSampler.Sample sample = sampler.start(tenantId);
final ClientConfigProperties config = connection.getConfig();
final Long timerId = config.getSendMessageTimeout() > 0 ? connection.getVertx().setTimer(config.getSendMessageTimeout(), id -> {
if (!result.future().isComplete()) {
handleSendMessageTimeout(message, config.getSendMessageTimeout(), deliveryRef.get(), sample, result, null);
}
}) : null;
deliveryRef.set(sender.send(message, deliveryUpdated -> {
if (timerId != null) {
connection.getVertx().cancelTimer(timerId);
}
final DeliveryState remoteState = deliveryUpdated.getRemoteState();
if (result.future().isComplete()) {
log.debug("ignoring received delivery update for message [ID: {}, address: {}]: waiting for the update has already timed out", messageId, getMessageAddress(message));
} else if (deliveryUpdated.remotelySettled()) {
logUpdatedDeliveryState(currentSpan, message, deliveryUpdated);
sample.completed(remoteState);
if (Accepted.class.isInstance(remoteState)) {
result.complete(deliveryUpdated);
} else {
if (mapUnacceptedOutcomeToErrorResult) {
result.handle(mapUnacceptedOutcomeToErrorResult(deliveryUpdated));
} else {
result.complete(deliveryUpdated);
}
}
} else {
logMessageSendingError("peer did not settle message [ID: {}, address: {}, remote state: {}], failing delivery", messageId, getMessageAddress(message), remoteState.getClass().getSimpleName());
final ServiceInvocationException e = new ServerErrorException(HttpURLConnection.HTTP_INTERNAL_ERROR, "peer did not settle message, failing delivery");
result.fail(e);
}
}));
log.trace("sent AT_LEAST_ONCE message [ID: {}, address: {}], remaining credit: {}, queued messages: {}", messageId, getMessageAddress(message), sender.getCredit(), sender.getQueued());
return result.future().onSuccess(delivery -> Tags.HTTP_STATUS.set(currentSpan, HttpURLConnection.HTTP_ACCEPTED)).onFailure(t -> {
TracingHelper.logError(currentSpan, t);
Tags.HTTP_STATUS.set(currentSpan, ServiceInvocationException.extractStatusCode(t));
}).onComplete(r -> currentSpan.finish());
}
use of io.vertx.core.Promise in project hono by eclipse.
the class MongoDbBasedCredentialServiceTest method testCredentialsDaoUsesIndex.
/**
* Verifies that the credentials DAO uses a proper index to retrieve credentials by auth-id and type.
*
* @param ctx The vert.x test context.
*/
@Test
public void testCredentialsDaoUsesIndex(final VertxTestContext ctx) {
final var tenantId = UUID.randomUUID().toString();
final MongoClient mongoClient = MongoDbTestUtils.getMongoClient(vertx, DB_NAME);
final var dto1 = CredentialsDto.forCreation(tenantId, UUID.randomUUID().toString(), List.of(Credentials.createPasswordCredential("device1a", "secret"), Credentials.createPSKCredential("device1b", "shared-secret")), UUID.randomUUID().toString());
final var dto2 = CredentialsDto.forCreation(tenantId, UUID.randomUUID().toString(), List.of(Credentials.createPasswordCredential("device2a", "secret"), Credentials.createPSKCredential("device2b", "shared-secret")), UUID.randomUUID().toString());
final var dto3 = CredentialsDto.forCreation(tenantId, UUID.randomUUID().toString(), List.of(Credentials.createPasswordCredential("device3a", "secret"), Credentials.createPSKCredential("device3b", "shared-secret")), UUID.randomUUID().toString());
final var dto4 = CredentialsDto.forCreation(UUID.randomUUID().toString(), UUID.randomUUID().toString(), List.of(Credentials.createPasswordCredential("device1a", "secret"), Credentials.createPSKCredential("device1b", "shared-secret")), UUID.randomUUID().toString());
credentialsDao.create(dto1, NoopSpan.INSTANCE.context()).compose(ok -> credentialsDao.create(dto2, NoopSpan.INSTANCE.context())).compose(ok -> credentialsDao.create(dto3, NoopSpan.INSTANCE.context())).compose(ok -> credentialsDao.create(dto4, NoopSpan.INSTANCE.context())).compose(ok -> {
final Promise<JsonObject> resultHandler = Promise.promise();
final var filter = MongoDbDocumentBuilder.builder().withTenantId(tenantId).withAuthId("device1a").withType(CredentialsConstants.SECRETS_TYPE_HASHED_PASSWORD).document();
final var commandRight = new JsonObject().put("find", "credentials").put("batchSize", 1).put("singleBatch", true).put("filter", filter).put("projection", MongoDbBasedCredentialsDao.PROJECTION_CREDS_BY_TYPE_AND_AUTH_ID);
final var explain = new JsonObject().put("explain", commandRight).put("verbosity", "executionStats");
mongoClient.runCommand("explain", explain, resultHandler);
return resultHandler.future();
}).onComplete(ctx.succeeding(result -> {
if (LOG.isTraceEnabled()) {
LOG.trace("result:{}{}", System.lineSeparator(), result.encodePrettily());
}
ctx.verify(() -> {
final var indexScan = (JsonObject) JsonPointer.from("/queryPlanner/winningPlan/inputStage/inputStage").queryJson(result);
assertThat(indexScan.getString("indexName")).isEqualTo(MongoDbBasedCredentialsDao.IDX_CREDENTIALS_TYPE_AND_AUTH_ID);
final var executionStats = result.getJsonObject("executionStats", new JsonObject());
// there are two credentials with auth-id "device1a" and type "hashed-password"
assertThat(executionStats.getInteger("totalKeysExamined")).isEqualTo(2);
assertThat(executionStats.getInteger("totalDocsExamined")).isEqualTo(2);
});
ctx.completeNow();
}));
}
use of io.vertx.core.Promise in project hono by eclipse.
the class MongoDbBasedDao method processSearchResource.
/**
* Finds resources such as tenant or device from the given MongoDB collection with the provided
* paging, filtering and sorting options.
* <p>
* A MongoDB aggregation pipeline is used to find the resources from the given MongoDB collection.
*
* @param pageSize The maximum number of results to include in a response.
* @param pageOffset The offset into the result set from which to include objects in the response.
* This allows to retrieve the whole result set page by page.
* @param filterDocument The document used for filtering the resources in a MongoDB aggregation pipeline.
* @param sortDocument The document used for sorting the resources in a MongoDB aggregation pipeline.
* @param resultMapper The mapper used for mapping the result for the search operation.
* @param <T> The type of the result namely {@link org.eclipse.hono.service.management.device.DeviceWithId} or
* {@link org.eclipse.hono.service.management.tenant.TenantWithId}
* @return A future indicating the outcome of the operation. The future will succeed if the search operation
* is successful and some resources are found. If no resources are found then the future will fail
* with a {@link ClientErrorException} with status {@link HttpURLConnection#HTTP_NOT_FOUND}.
* The future will be failed with a {@link ServiceInvocationException} if the query could not be executed.
* @throws NullPointerException if any of the parameters is {@code null}.
* @throws IllegalArgumentException if page size is <= 0 or page offset is < 0.
* @see <a href="https://docs.mongodb.com/manual/core/aggregation-pipeline">MongoDB Aggregation Pipeline</a>
*/
protected <T> Future<SearchResult<T>> processSearchResource(final int pageSize, final int pageOffset, final JsonObject filterDocument, final JsonObject sortDocument, final Function<JsonObject, List<T>> resultMapper) {
if (pageSize <= 0) {
throw new IllegalArgumentException("page size must be a positive integer");
}
if (pageOffset < 0) {
throw new IllegalArgumentException("page offset must not be negative");
}
Objects.requireNonNull(filterDocument);
Objects.requireNonNull(sortDocument);
Objects.requireNonNull(resultMapper);
final JsonArray aggregationPipelineQuery = getSearchResourceQuery(pageSize, pageOffset, filterDocument, sortDocument);
final Promise<JsonObject> searchPromise = Promise.promise();
if (LOG.isTraceEnabled()) {
LOG.trace("searching resources using aggregation pipeline:{}{}", System.lineSeparator(), aggregationPipelineQuery.encodePrettily());
}
mongoClient.aggregate(collectionName, aggregationPipelineQuery).exceptionHandler(searchPromise::fail).handler(searchPromise::complete);
return searchPromise.future().onSuccess(searchResult -> {
if (LOG.isTraceEnabled()) {
LOG.trace("search result:{}{}", System.lineSeparator(), searchResult.encodePrettily());
}
}).map(result -> Optional.ofNullable(result.getInteger(RegistryManagementConstants.FIELD_RESULT_SET_SIZE)).filter(total -> total > 0).map(total -> new SearchResult<>(total, resultMapper.apply(result))).orElseThrow(() -> new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND))).recover(this::mapError);
}
use of io.vertx.core.Promise in project hono by eclipse.
the class ClasspathSchemaCreator method runScript.
private Future<Void> runScript(final JdbcProperties jdbcProperties, final String script, final SpanContext ctx) {
final JDBCClient jdbcClient = JdbcProperties.dataSource(vertx, jdbcProperties);
final Promise<Void> clientCloseTracker = Promise.promise();
SQL.runTransactionally(jdbcClient, tracer, ctx, (connection, context) -> {
final var expanded = Statement.statement(script).expand();
log.debug("Creating database schema in [{}] with script: {}", jdbcProperties.getUrl(), expanded);
return expanded.query(jdbcClient).recover(SQL::translateException);
}).onComplete(ar -> jdbcClient.close(clientCloseTracker));
return clientCloseTracker.future();
}
Aggregations