/* * Copyright (c) 2010-2017. Axon Framework * 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.axonframework.amqp.eventhandling; import com.rabbitmq.client.AMQP; import org.axonframework.common.Assert; import org.axonframework.common.DateTimeUtils; import org.axonframework.eventhandling.EventMessage; import org.axonframework.eventhandling.GenericEventMessage; import org.axonframework.eventsourcing.DomainEventMessage; import org.axonframework.eventsourcing.GenericDomainEventMessage; import org.axonframework.messaging.MetaData; import org.axonframework.serialization.*; import java.util.*; import static org.axonframework.serialization.MessageSerializer.serializePayload; /** * Default implementation of the AMQPMessageConverter interface. This implementation will suffice in most cases. It * passes all meta-data entries as headers (with 'axon-metadata-' prefix) to the message. Other message-specific * attributes are also added as meta data. The message payload is serialized using the configured serializer and passed * as the message body. * * @author Allard Buijze */ public class DefaultAMQPMessageConverter implements AMQPMessageConverter { private final Serializer serializer; private final RoutingKeyResolver routingKeyResolver; private final boolean durable; /** * Initializes the AMQPMessageConverter with the given {@code serializer}, using a {@link * PackageRoutingKeyResolver} and requesting durable dispatching. * * @param serializer The serializer to serialize the Event Message's payload with */ public DefaultAMQPMessageConverter(Serializer serializer) { this(serializer, new PackageRoutingKeyResolver(), true); } /** * Initializes the AMQPMessageConverter with the given {@code serializer}, {@code routingKeyResolver} and * requesting durable dispatching when {@code durable} is {@code true}. * * @param serializer The serializer to serialize the Event Message's payload and Meta Data with * @param routingKeyResolver The strategy to use to resolve routing keys for Event Messages * @param durable Whether to request durable message dispatching */ public DefaultAMQPMessageConverter(Serializer serializer, RoutingKeyResolver routingKeyResolver, boolean durable) { Assert.notNull(serializer, () -> "Serializer may not be null"); Assert.notNull(routingKeyResolver, () -> "RoutingKeyResolver may not be null"); this.serializer = serializer; this.routingKeyResolver = routingKeyResolver; this.durable = durable; } @Override public AMQPMessage createAMQPMessage(EventMessage<?> eventMessage) { SerializedObject<byte[]> serializedObject = serializePayload(eventMessage, serializer, byte[].class); String routingKey = routingKeyResolver.resolveRoutingKey(eventMessage); AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder(); Map<String, Object> headers = new HashMap<>(); eventMessage.getMetaData().forEach((k, v) -> headers.put("axon-metadata-" + k, v)); headers.put("axon-message-id", eventMessage.getIdentifier()); headers.put("axon-message-type", serializedObject.getType().getName()); headers.put("axon-message-revision", serializedObject.getType().getRevision()); headers.put("axon-message-timestamp", eventMessage.getTimestamp().toString()); if (eventMessage instanceof DomainEventMessage) { headers.put("axon-message-aggregate-id", ((DomainEventMessage) eventMessage).getAggregateIdentifier()); headers.put("axon-message-aggregate-seq", ((DomainEventMessage) eventMessage).getSequenceNumber()); headers.put("axon-message-aggregate-type", ((DomainEventMessage) eventMessage).getType()); } properties.headers(headers); if (durable) { properties.deliveryMode(2); } return new AMQPMessage(serializedObject.getData(), routingKey, properties.build(), false, false); } @Override public Optional<EventMessage<?>> readAMQPMessage(byte[] messageBody, Map<String, Object> headers) { if (!headers.keySet().containsAll(Arrays.asList("axon-message-id", "axon-message-type"))) { return Optional.empty(); } Map<String, Object> metaData = new HashMap<>(); headers.forEach((k, v) -> { if (k.startsWith("axon-metadata-")) { metaData.put(k.substring("axon-metadata-".length()), v); } }); SimpleSerializedObject<byte[]> serializedMessage = new SimpleSerializedObject<>(messageBody, byte[].class, Objects.toString(headers.get("axon-message-type")), Objects.toString(headers.get("axon-message-revision"), null)); SerializedMessage<?> message = new SerializedMessage<>(Objects.toString(headers.get("axon-message-id")), new LazyDeserializingObject<>(serializedMessage, serializer), new LazyDeserializingObject<>(MetaData.from(metaData))); String timestamp = Objects.toString(headers.get("axon-message-timestamp")); if (headers.containsKey("axon-message-aggregate-id")) { return Optional.of(new GenericDomainEventMessage<>(Objects.toString(headers.get("axon-message-aggregate-type")), Objects.toString(headers.get("axon-message-aggregate-id")), (Long) headers.get("axon-message-aggregate-seq"), message, () -> DateTimeUtils.parseInstant(timestamp))); } else { return Optional.of(new GenericEventMessage<>(message, () -> DateTimeUtils.parseInstant(timestamp))); } } }