org.eclipse.ditto.services.connectivity.messaging.rabbitmq.RabbitMQConsumerActor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ditto.services.connectivity.messaging.rabbitmq.RabbitMQConsumerActor.java

Source

/*
 * Copyright (c) 2017 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.ditto.services.connectivity.messaging.rabbitmq;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.eclipse.ditto.model.base.auth.AuthorizationContext;
import org.eclipse.ditto.model.base.common.CharsetDeterminer;
import org.eclipse.ditto.model.base.exceptions.DittoRuntimeException;
import org.eclipse.ditto.model.base.headers.DittoHeaderDefinition;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.connectivity.ConnectionId;
import org.eclipse.ditto.model.connectivity.Enforcement;
import org.eclipse.ditto.model.connectivity.HeaderMapping;
import org.eclipse.ditto.model.connectivity.ResourceStatus;
import org.eclipse.ditto.model.placeholders.EnforcementFactoryFactory;
import org.eclipse.ditto.model.placeholders.EnforcementFilterFactory;
import org.eclipse.ditto.model.placeholders.PlaceholderFactory;
import org.eclipse.ditto.services.connectivity.messaging.BaseConsumerActor;
import org.eclipse.ditto.services.connectivity.messaging.internal.RetrieveAddressStatus;
import org.eclipse.ditto.services.models.connectivity.ExternalMessage;
import org.eclipse.ditto.services.models.connectivity.ExternalMessageBuilder;
import org.eclipse.ditto.services.models.connectivity.ExternalMessageFactory;
import org.eclipse.ditto.services.utils.akka.LogUtil;

import com.rabbitmq.client.BasicProperties;
import com.rabbitmq.client.Delivery;
import com.rabbitmq.client.Envelope;

import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.DiagnosticLoggingAdapter;
import akka.japi.pf.ReceiveBuilder;

/**
 * Actor which receives message from an RabbitMQ source and forwards them to a {@code MessageMappingProcessorActor}.
 */
public final class RabbitMQConsumerActor extends BaseConsumerActor {

    private static final String MESSAGE_ID_HEADER = "messageId";
    private static final String CONTENT_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";

    private final DiagnosticLoggingAdapter log = LogUtil.obtain(this);

    private final EnforcementFilterFactory<Map<String, String>, CharSequence> headerEnforcementFilterFactory;

    @SuppressWarnings("unused")
    private RabbitMQConsumerActor(final ConnectionId connectionId, final String sourceAddress,
            final ActorRef messageMappingProcessor, final AuthorizationContext authorizationContext,
            @Nullable final Enforcement enforcement, @Nullable final HeaderMapping headerMapping) {
        super(connectionId, sourceAddress, messageMappingProcessor, authorizationContext, headerMapping);
        headerEnforcementFilterFactory = enforcement != null
                ? EnforcementFactoryFactory.newEnforcementFilterFactory(enforcement,
                        PlaceholderFactory.newHeadersPlaceholder())
                : input -> null;
    }

    /**
     * Creates Akka configuration object {@link Props} for this {@code RabbitMQConsumerActor}.
     *
     * @param connectionId ID of the connection
     * @param source the source of messages
     * @param messageMappingProcessor the message mapping processor where received messages are forwarded to
     * @param authorizationContext the authorization context of this source
     * @param enforcement the enforcement configuration
     * @param headerMapping optional header mappings
     * @return the Akka configuration Props object.
     */
    static Props props(final String source, final ActorRef messageMappingProcessor,
            final AuthorizationContext authorizationContext, @Nullable final Enforcement enforcement,
            @Nullable final HeaderMapping headerMapping, final ConnectionId connectionId) {

        return Props.create(RabbitMQConsumerActor.class, connectionId, source, messageMappingProcessor,
                authorizationContext, enforcement, headerMapping);
    }

    @Override
    public Receive createReceive() {
        return ReceiveBuilder.create().match(Delivery.class, this::handleDelivery)
                .match(ResourceStatus.class, this::handleAddressStatus)
                .match(RetrieveAddressStatus.class, ram -> getSender().tell(getCurrentSourceStatus(), getSelf()))
                .matchAny(m -> {
                    log.warning("Unknown message: {}", m);
                    unhandled(m);
                }).build();
    }

    private void handleDelivery(final Delivery delivery) {
        final BasicProperties properties = delivery.getProperties();
        final Envelope envelope = delivery.getEnvelope();
        final byte[] body = delivery.getBody();
        final String hashKey = envelope.getExchange() + ":" + envelope.getRoutingKey();

        Map<String, String> headers = null;
        try {
            final String correlationId = properties.getCorrelationId();
            LogUtil.enhanceLogWithCorrelationId(log, correlationId);
            if (log.isDebugEnabled()) {
                log.debug("Received message from RabbitMQ ({}//{}): {}", envelope, properties,
                        new String(body, StandardCharsets.UTF_8));
            }
            headers = extractHeadersFromMessage(properties, envelope);
            final ExternalMessageBuilder externalMessageBuilder = ExternalMessageFactory
                    .newExternalMessageBuilder(headers);
            final String contentType = properties.getContentType();
            final String text = new String(body, CharsetDeterminer.getInstance().apply(contentType));
            if (shouldBeInterpretedAsBytes(contentType)) {
                externalMessageBuilder.withBytes(body);
            } else {
                externalMessageBuilder.withTextAndBytes(text, body);
            }
            externalMessageBuilder.withAuthorizationContext(authorizationContext);
            externalMessageBuilder.withEnforcement(headerEnforcementFilterFactory.getFilter(headers));
            externalMessageBuilder.withHeaderMapping(headerMapping);
            externalMessageBuilder.withSourceAddress(sourceAddress);
            final ExternalMessage externalMessage = externalMessageBuilder.build();
            inboundMonitor.success(externalMessage);
            forwardToMappingActor(externalMessage, hashKey);
        } catch (final DittoRuntimeException e) {
            log.warning("Processing delivery {} failed: {}", envelope.getDeliveryTag(), e.getMessage());
            if (headers != null) {
                // send response if headers were extracted successfully
                forwardToMappingActor(e.setDittoHeaders(DittoHeaders.of(headers)), hashKey);
                inboundMonitor.failure(headers, e);
            } else {
                inboundMonitor.failure(e);
            }
        } catch (final Exception e) {
            log.warning("Processing delivery {} failed: {}", envelope.getDeliveryTag(), e.getMessage());
            if (headers != null) {
                inboundMonitor.exception(headers, e);
            } else {
                inboundMonitor.exception(e);
            }
        }
    }

    private static boolean shouldBeInterpretedAsBytes(@Nullable final String contentType) {
        return contentType != null && contentType.startsWith(CONTENT_TYPE_APPLICATION_OCTET_STREAM);
    }

    private static Map<String, String> extractHeadersFromMessage(final BasicProperties properties,
            final Envelope envelope) {

        final Map<String, String> headersFromProperties = getHeadersFromProperties(properties.getHeaders());

        // set headers specific to rmq messages
        if (properties.getReplyTo() != null) {
            headersFromProperties.put(ExternalMessage.REPLY_TO_HEADER, properties.getReplyTo());
        }
        if (properties.getCorrelationId() != null) {
            headersFromProperties.put(DittoHeaderDefinition.CORRELATION_ID.getKey(), properties.getCorrelationId());
        }
        if (properties.getContentType() != null) {
            headersFromProperties.put(ExternalMessage.CONTENT_TYPE_HEADER, properties.getContentType());
        }
        headersFromProperties.put(MESSAGE_ID_HEADER, Long.toString(envelope.getDeliveryTag()));

        return headersFromProperties;
    }

    private static Map<String, String> getHeadersFromProperties(@Nullable final Map<String, Object> originalProps) {
        if (null != originalProps) {
            return originalProps.entrySet().stream().filter(entry -> Objects.nonNull(entry.getValue()))
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> String.valueOf(entry.getValue())));
        }

        return new HashMap<>();
    }

}