/** * Create an ErrorCondition with the given error condition value (which will be converted to the required Symbol type) * and error description string. * * See {@link #condition(Symbol, String)} for more details. * * @param condition * the error condition value, to be converted to a Symbol * @param description * String description of the error, may be null * @return the condition */ public static ErrorCondition condition(String condition, String description) { return condition(Symbol.valueOf(condition), description); }
/** * Creates an ErrorCondition using the given throwable to provide an error condition value and * description. All throwables that are not service invocation exceptions will be mapped to {@link AmqpError#PRECONDITION_FAILED}. * * @param t The throwable to map to an error condition. * @return The ErrorCondition. */ static ErrorCondition getErrorCondition(final Throwable t) { if (ServiceInvocationException.class.isInstance(t)) { final ServiceInvocationException error = (ServiceInvocationException) t; switch (error.getErrorCode()) { case HttpURLConnection.HTTP_BAD_REQUEST: return ProtonHelper.condition(Constants.AMQP_BAD_REQUEST, error.getMessage()); case HttpURLConnection.HTTP_FORBIDDEN: return ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, error.getMessage()); default: return ProtonHelper.condition(AmqpError.PRECONDITION_FAILED, error.getMessage()); } } else { return ProtonHelper.condition(AmqpError.PRECONDITION_FAILED, t.getMessage()); } } }
/** * Gets an AMQP {@link ErrorCondition} based on this exception's error and description. * * @return The condition. */ public ErrorCondition asErrorCondition() { return ProtonHelper.condition(error.toString(), getMessage()); } }
@Override protected void handleReceiverOpen(final ProtonConnection con, final ProtonReceiver receiver) { receiver.setCondition(ProtonHelper.condition(AmqpError.NOT_ALLOWED, "cannot write to node")); receiver.close(); }
/** * Gets an AMQP {@link ErrorCondition} based on this exception's error and description. * * @return The condition. */ public ErrorCondition asErrorCondition() { return ProtonHelper.condition(error.toString(), getMessage()); } }
@Override public void onLinkAttach(final ProtonConnection con, final ProtonSender sender, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data retrieval, closing link.", getName()); sender.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be read from")); sender.close(); }
@Override public void onLinkAttach(final ProtonConnection con, final ProtonSender sender, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data retrieval, closing link.", getName()); sender.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be read from")); sender.close(); }
@Override public void onLinkAttach(final ProtonSender sender, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data retrieval, closing link.", getName()); sender.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be read from")); sender.close(); }
@Override public void onLinkAttach(final ProtonConnection con, final ProtonReceiver receiver, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data upload, closing link.", getName()); receiver.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be written to")); receiver.close(); }
@Override public void onLinkAttach(final ProtonConnection con, final ProtonReceiver receiver, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data upload, closing link.", getName()); receiver.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be written to")); receiver.close(); }
@Override public void onLinkAttach(final ProtonReceiver receiver, final ResourceIdentifier targetResource) { logger.info("Endpoint [{}] does not support data upload, closing link.", getName()); receiver.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be written to")); receiver.close(); }
/** * Closes a link for an unknown target address. * <p> * The link is closed with AMQP error code <em>amqp:not-found</em>. * * @param con The connection that the link belongs to. * @param link The link. * @param address The unknown target address. */ protected final void handleUnknownEndpoint(final ProtonConnection con, final ProtonLink<?> link, final ResourceIdentifier address) { LOG.info("client [container: {}] wants to establish link for unknown endpoint [address: {}]", con.getRemoteContainer(), address); link.setCondition( ProtonHelper.condition( AmqpError.NOT_FOUND, String.format("no endpoint registered for address %s", address))); link.close(); }
/** * Closes a link for an unknown target address. * <p> * The link is closed with AMQP error code <em>amqp:not-found</em>. * * @param con The connection that the link belongs to. * @param link The link. * @param address The unknown target address. */ protected final void handleUnknownEndpoint(final ProtonConnection con, final ProtonLink<?> link, final ResourceIdentifier address) { LOG.info("client [container: {}] wants to establish link for unknown endpoint [address: {}]", con.getRemoteContainer(), address); link.setCondition( ProtonHelper.condition( AmqpError.NOT_FOUND, String.format("no endpoint registered for address %s", address))); link.close(); }
private static void handleUnknownEndpoint(final ProtonConnection con, final ProtonLink<?> link, final ResourceIdentifier address) { LOG.info("client [{}] wants to establish link for unknown endpoint [address: {}]", con.getRemoteContainer(), address); link.setCondition( condition(AmqpError.NOT_FOUND.toString(), String.format("no endpoint registered for address %s", address))); link.close(); }
@Override public final void onLinkAttach(final ProtonReceiver receiver, final ResourceIdentifier targetAddress) { final String linkId = UUID.randomUUID().toString(); final UpstreamReceiver link = UpstreamReceiver.newUpstreamReceiver(linkId, receiver, getEndpointQos()); downstreamAdapter.onClientAttach(link, s -> { if (s.succeeded()) { receiver.closeHandler(clientDetached -> { // client has closed link -> inform TelemetryAdapter about client detach onLinkDetach(link); downstreamAdapter.onClientDetach(link); }).handler((delivery, message) -> { if (passesFormalVerification(targetAddress, message)) { forwardMessage(link, delivery, message); } else { MessageHelper.rejected(delivery, AmqpError.DECODE_ERROR.toString(), "malformed message"); onLinkDetach(link, condition(AmqpError.DECODE_ERROR.toString(), "invalid message received")); } }).open(); logger.debug("accepted link from telemetry client [{}]", linkId); } else { // we cannot connect to downstream container, reject client link.close(condition(AmqpError.PRECONDITION_FAILED, "no consumer available for target")); } }); }
@Override public final void onLinkAttach(final ProtonConnection con, final ProtonSender sender, final ResourceIdentifier targetResource) { if (ProtonQoS.AT_LEAST_ONCE.equals(sender.getRemoteQoS())) { final HonoUser user = Constants.getClientPrincipal(con); sender.setQoS(ProtonQoS.AT_LEAST_ONCE).open(); logger.debug("transferring token to client..."); final Message tokenMsg = ProtonHelper.message(user.getToken()); MessageHelper.addProperty(tokenMsg, AuthenticationConstants.APPLICATION_PROPERTY_TYPE, AuthenticationConstants.TYPE_AMQP_JWT); sender.send(tokenMsg, disposition -> { if (disposition.remotelySettled()) { logger.debug("successfully transferred auth token to client"); } else { logger.debug("failed to transfer auth token to client"); } sender.close(); }); } else { onLinkDetach(sender, ProtonHelper.condition(AmqpError.INVALID_FIELD, "supports AT_LEAST_ONCE delivery mode only")); } }
private void forwardMessage(final UpstreamReceiver link, final ProtonDelivery delivery, final Message msg) { final ResourceIdentifier messageAddress = ResourceIdentifier.fromString(getAnnotation(msg, APP_PROPERTY_RESOURCE, String.class)); checkDeviceExists(messageAddress, deviceExists -> { if (deviceExists) { downstreamAdapter.processMessage(link, delivery, msg); } else { logger.debug("device {}/{} does not exist, closing link", messageAddress.getTenantId(), messageAddress.getResourceId()); MessageHelper.rejected(delivery, AmqpError.PRECONDITION_FAILED.toString(), "device does not exist"); link.close(condition(AmqpError.PRECONDITION_FAILED.toString(), "device does not exist")); } }); }
@Override public void onLinkAttach(final ProtonSender sender, final ResourceIdentifier targetResource) { /* note: we "misuse" deviceId part of the resource as reply address here */ if (targetResource.getResourceId() == null) { logger.debug("link target provided in client's link ATTACH does not match pattern \"registration/<tenant>/<reply-address>\""); sender.setCondition(condition(AmqpError.INVALID_FIELD.toString(), "link target must have the following format registration/<tenant>/<reply-address>")); sender.close(); } else { logger.debug("establishing sender link with client [{}]", MessageHelper.getLinkName(sender)); final MessageConsumer<JsonObject> replyConsumer = vertx.eventBus().consumer(targetResource.toString(), message -> { // TODO check for correct session here...? logger.trace("forwarding reply to client: {}", message.body()); final Message amqpReply = RegistrationConstants.getAmqpReply(message); sender.send(amqpReply); }); sender.closeHandler(senderClosed -> { replyConsumer.unregister(); senderClosed.result().close(); final String linkName = MessageHelper.getLinkName(sender); logger.debug("receiver closed link [{}], removing associated event bus consumer [{}]", linkName, replyConsumer.address()); }); sender.setQoS(ProtonQoS.AT_LEAST_ONCE).open(); } }
/** * Closes an expired client connection. * <p> * A connection is considered expired if the {@link HonoUser#isExpired()} method * of the user principal attached to the connection returns {@code true}. * * @param con The client connection. */ protected final void closeExpiredConnection(final ProtonConnection con) { if (!con.isDisconnected()) { final HonoUser clientPrincipal = Constants.getClientPrincipal(con); if (clientPrincipal != null) { LOG.debug("client's [{}] access token has expired, closing connection", clientPrincipal.getName()); con.disconnectHandler(null); con.closeHandler(null); con.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, "access token expired")); con.close(); con.disconnect(); publishConnectionClosedEvent(con); } } }
/** * Closes an expired client connection. * <p> * A connection is considered expired if the {@link HonoUser#isExpired()} method * of the user principal attached to the connection returns {@code true}. * * @param con The client connection. */ protected final void closeExpiredConnection(final ProtonConnection con) { if (!con.isDisconnected()) { final HonoUser clientPrincipal = Constants.getClientPrincipal(con); if (clientPrincipal != null) { LOG.debug("client's [{}] access token has expired, closing connection", clientPrincipal.getName()); con.disconnectHandler(null); con.closeHandler(null); con.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, "access token expired")); con.close(); con.disconnect(); publishConnectionClosedEvent(con); } } }