org.fabric3.binding.zeromq.runtime.broker.ZeroMQWireBrokerImpl Maven / Gradle / Ivy
The newest version!
/*
* Fabric3 Copyright (c) 2009-2013 Metaform Systems
*
* Fabric3 is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version, with the following exception:
*
* Linking this software statically or dynamically with other modules is making
* a combined work based on this software. Thus, the terms and conditions of the
* GNU General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules to produce an
* executable, regardless of the license terms of these independent modules, and
* to copy and distribute the resulting executable under terms of your choice,
* provided that you also meet, for each linked independent module, the terms
* and conditions of the license of that module. An independent module is a
* module which is not derived from or based on this software. If you modify
* this software, you may extend this exception to your version of the software,
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Fabric3 is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Fabric3. If not, see .
*/
package org.fabric3.binding.zeromq.runtime.broker;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import org.fabric3.spi.invocation.CallbackReference;
import org.oasisopen.sca.annotation.Init;
import org.oasisopen.sca.annotation.Property;
import org.oasisopen.sca.annotation.Reference;
import org.oasisopen.sca.annotation.Service;
import org.fabric3.api.annotation.monitor.Monitor;
import org.fabric3.binding.zeromq.common.SocketAddressDefinition;
import org.fabric3.binding.zeromq.common.ZeroMQMetadata;
import org.fabric3.binding.zeromq.runtime.BrokerException;
import org.fabric3.binding.zeromq.runtime.MessagingMonitor;
import org.fabric3.binding.zeromq.runtime.SocketAddress;
import org.fabric3.binding.zeromq.runtime.ZeroMQWireBroker;
import org.fabric3.binding.zeromq.runtime.context.ContextManager;
import org.fabric3.binding.zeromq.runtime.federation.AddressAnnouncement;
import org.fabric3.binding.zeromq.runtime.federation.AddressCache;
import org.fabric3.binding.zeromq.runtime.interceptor.OneWayInterceptor;
import org.fabric3.binding.zeromq.runtime.interceptor.RequestReplyInterceptor;
import org.fabric3.binding.zeromq.runtime.interceptor.UnwrappingInterceptor;
import org.fabric3.binding.zeromq.runtime.interceptor.WrappingInterceptor;
import org.fabric3.binding.zeromq.runtime.management.ZeroMQManagementService;
import org.fabric3.binding.zeromq.runtime.message.DelegatingOneWaySender;
import org.fabric3.binding.zeromq.runtime.message.DynamicOneWaySender;
import org.fabric3.binding.zeromq.runtime.message.NonReliableOneWayReceiver;
import org.fabric3.binding.zeromq.runtime.message.NonReliableOneWaySender;
import org.fabric3.binding.zeromq.runtime.message.NonReliableRequestReplyReceiver;
import org.fabric3.binding.zeromq.runtime.message.NonReliableRequestReplySender;
import org.fabric3.binding.zeromq.runtime.message.OneWaySender;
import org.fabric3.binding.zeromq.runtime.message.Receiver;
import org.fabric3.binding.zeromq.runtime.message.RequestReplySender;
import org.fabric3.binding.zeromq.runtime.message.Sender;
import org.fabric3.host.runtime.HostInfo;
import org.fabric3.model.type.contract.DataType;
import org.fabric3.spi.event.EventService;
import org.fabric3.spi.event.Fabric3EventListener;
import org.fabric3.spi.event.RuntimeStop;
import org.fabric3.spi.host.Port;
import org.fabric3.spi.host.PortAllocationException;
import org.fabric3.spi.host.PortAllocator;
import org.fabric3.spi.invocation.WorkContext;
import org.fabric3.spi.model.physical.ParameterTypeHelper;
import org.fabric3.spi.model.physical.PhysicalOperationDefinition;
import org.fabric3.spi.model.type.java.JavaClass;
import org.fabric3.spi.wire.Interceptor;
import org.fabric3.spi.wire.InterceptorCreationException;
import org.fabric3.spi.wire.InvocationChain;
import org.fabric3.spi.wire.TransformerInterceptorFactory;
/**
*
*/
@Service(ZeroMQWireBroker.class)
public class ZeroMQWireBrokerImpl implements ZeroMQWireBroker, DynamicOneWaySender, Fabric3EventListener {
private static final DataType BYTE_TYPE = new JavaClass(byte[].class);
private static final DataType EMPTY_TYPE = new JavaClass(Void.class);
List> TRANSPORT_TYPES; // the transport type is a byte array
private static final String ZMQ = "zmq";
private ContextManager manager;
private AddressCache addressCache;
private PortAllocator allocator;
private EventService eventService;
private HostInfo info;
private ZeroMQManagementService managementService;
private ExecutorService executorService;
private MessagingMonitor monitor;
private long pollTimeout = 10000000;
private TransformerInterceptorFactory interceptorFactory;
private String host;
private String hostAddress;
private Map senders = new HashMap();
private Map receivers = new HashMap();
public ZeroMQWireBrokerImpl(@Reference ContextManager manager,
@Reference AddressCache addressCache,
@Reference PortAllocator allocator,
@Reference ExecutorService executorService,
@Reference ZeroMQManagementService managementService,
@Reference EventService eventService,
@Reference TransformerInterceptorFactory interceptorFactory,
@Reference HostInfo info,
@Monitor MessagingMonitor monitor) throws UnknownHostException {
this.manager = manager;
this.addressCache = addressCache;
this.allocator = allocator;
this.executorService = executorService;
this.managementService = managementService;
this.eventService = eventService;
this.interceptorFactory = interceptorFactory;
this.info = info;
this.monitor = monitor;
this.host = InetAddress.getLocalHost().getHostAddress();
TRANSPORT_TYPES = new ArrayList>();
TRANSPORT_TYPES.add(BYTE_TYPE);
this.hostAddress = InetAddress.getLocalHost().getHostAddress();
}
/**
* Sets the timeout in milliseconds for polling operations.
*
* @param timeout the timeout in milliseconds for polling operations
*/
@Property(required = false)
public void setPollTimeout(long timeout) {
this.pollTimeout = timeout * 1000; // convert milliseconds to microseconds
}
/**
* Sets this host to bind the publisher to.
*
* @param host the host
*/
@Property(required = false)
public void setHost(String host) {
this.host = host;
}
@Init
public void init() {
eventService.subscribe(RuntimeStop.class, this);
}
public void connectToSender(String id, URI uri, List chains, ZeroMQMetadata metadata, ClassLoader loader)
throws BrokerException {
SenderHolder holder;
if (ZMQ.equals(uri.getScheme())) {
DelegatingOneWaySender sender = new DelegatingOneWaySender(id, this, metadata);
holder = new SenderHolder(sender);
} else {
holder = senders.get(uri.toString());
}
if (holder == null) {
boolean oneWay = isOneWay(chains, uri);
holder = createSender(uri.toString(), oneWay, metadata);
managementService.registerSender(id, holder.getSender());
}
for (int i = 0, chainsSize = chains.size(); i < chainsSize; i++) {
InvocationChain chain = chains.get(i);
try {
PhysicalOperationDefinition physicalOperation = chain.getPhysicalOperation();
List> sourceTypes = createTypes(physicalOperation, loader);
Interceptor interceptor = interceptorFactory.createInterceptor(physicalOperation, sourceTypes, TRANSPORT_TYPES, loader, loader);
chain.addInterceptor(interceptor);
chain.addInterceptor(new UnwrappingInterceptor());
} catch (InterceptorCreationException e) {
throw new BrokerException(e);
}
Interceptor interceptor = createInterceptor(holder, i);
chain.addInterceptor(interceptor);
}
holder.getIds().add(id);
}
public void releaseSender(String id, URI uri) throws BrokerException {
SenderHolder holder = senders.get(uri.toString());
if (holder == null) {
if (!ZMQ.equals(uri.getScheme())) {
// callback holders are dynamically created and it is possible for a sender to be released before an invocation is dispatched to it
throw new BrokerException("Sender not found for " + uri);
} else {
return;
}
}
holder.getIds().remove(id);
if (holder.getIds().isEmpty()) {
senders.remove(uri.toString());
Sender sender = holder.getSender();
sender.stop();
managementService.unregisterSender(id);
}
}
public void connectToReceiver(URI uri, List chains, ZeroMQMetadata metadata, ClassLoader loader)
throws BrokerException {
if (receivers.containsKey(uri.toString())) {
throw new BrokerException("Receiver already defined for " + uri);
}
try {
String endpointId = uri.toString();
String runtimeName = info.getRuntimeName();
SocketAddress address;
if (metadata.getSocketAddresses() != null && !metadata.getSocketAddresses().isEmpty()) {
// bind using specified address and port
if (metadata.getSocketAddresses().size() != 1) {
throw new BrokerException("Only one socket address can be specified");
}
SocketAddressDefinition addressDefinition = metadata.getSocketAddresses().get(0);
String specifiedHost = addressDefinition.getHost();
if ("localhost".equals(specifiedHost)) {
specifiedHost = hostAddress;
}
int portNumber = addressDefinition.getPort();
Port port = allocator.reserve(endpointId, ZMQ, portNumber);
address = new SocketAddress(runtimeName, "tcp", specifiedHost, port);
} else {
// bind to a randomly allocated port
Port port = allocator.allocate(endpointId, ZMQ);
address = new SocketAddress(runtimeName, "tcp", host, port);
}
addTransformer(chains, loader);
boolean oneWay = isOneWay(chains, uri);
Receiver receiver;
if (oneWay) {
receiver = new NonReliableOneWayReceiver(manager, address, chains, executorService, metadata, monitor);
} else {
receiver = new NonReliableRequestReplyReceiver(manager, address, chains, executorService, pollTimeout, metadata, monitor);
}
receiver.start();
AddressAnnouncement event = new AddressAnnouncement(endpointId, AddressAnnouncement.Type.ACTIVATED, address);
addressCache.publish(event);
receivers.put(uri.toString(), receiver);
String id = createReceiverId(uri);
managementService.registerReceiver(id, receiver);
monitor.onProvisionEndpoint(id);
} catch (PortAllocationException
e) {
throw new BrokerException("Error allocating port for " + uri, e);
}
}
public void releaseReceiver(URI uri) throws BrokerException {
Receiver receiver = receivers.remove(uri.toString());
if (receiver == null) {
throw new BrokerException("Receiver not found for " + uri);
}
String endpointId = uri.toString();
SocketAddress address = receiver.getAddress();
AddressAnnouncement event = new AddressAnnouncement(endpointId, AddressAnnouncement.Type.REMOVED, address);
addressCache.publish(event);
receiver.stop();
allocator.release(endpointId);
String id = createReceiverId(uri);
managementService.unregisterReceiver(id);
monitor.onRemoveEndpoint(id);
}
public void send(byte[] message, int index, WorkContext context, ZeroMQMetadata metadata) {
CallbackReference callbackReference = context.peekCallbackReference();
if (callbackReference == null) {
monitor.error("Callback reference not found");
return;
}
String callback = callbackReference.getServiceUri();
SenderHolder holder = senders.get(callback);
if (holder == null) {
holder = createSender(callback, true, metadata);
}
Sender sender = holder.getSender();
if (sender instanceof OneWaySender) {
((OneWaySender) sender).send(message, index, context);
} else {
monitor.error("Callback sender is not a one-way type: " + holder.getClass().getName());
}
}
public void startAll() {
for (Receiver receiver : receivers.values()) {
receiver.start();
}
for (SenderHolder holder : senders.values()) {
holder.getSender().start();
}
}
public void stopAll() {
for (Receiver receiver : receivers.values()) {
receiver.stop();
}
for (SenderHolder holder : senders.values()) {
holder.getSender().stop();
}
}
public void start() {
// no-op
}
public void stop() {
// no-op
}
public String getId() {
return "ZeroMQWireBroker";
}
public void onUpdate(List addresses) {
// no-op
}
public void onEvent(RuntimeStop event) {
stopAll();
}
private SenderHolder createSender(String endpointId, boolean oneWay, ZeroMQMetadata metadata) {
List addresses = new ArrayList();
boolean refresh;
if (metadata.getSocketAddresses() != null) {
// service addresses to connect to are explicitly configured in the binding definition
refresh = false;
for (SocketAddressDefinition addressDefinition : metadata.getSocketAddresses()) {
Port port = new SpecifiedPort(addressDefinition.getPort());
String specifiedHost = addressDefinition.getHost();
if ("localhost".equals(specifiedHost)) {
specifiedHost = hostAddress;
}
SocketAddress socketAddress = new SocketAddress("synthetic", "tcp", specifiedHost, port);
addresses.add(socketAddress);
}
} else {
// services addresses to connect to are not specified in the binding, retrieve them from the federation layer
refresh = true;
addresses = addressCache.getActiveAddresses(endpointId);
}
Sender sender;
if (oneWay) {
sender = new NonReliableOneWaySender(endpointId, manager, addresses, pollTimeout, metadata, monitor);
} else {
sender = new NonReliableRequestReplySender(endpointId, manager, addresses, pollTimeout, metadata, monitor);
}
SenderHolder holder = new SenderHolder(sender);
sender.start();
if (refresh) {
// don't subscribe for updates if the sockets are explicitly configured
addressCache.subscribe(endpointId, sender);
}
senders.put(endpointId, holder);
return holder;
}
/**
* Determines if the wire is one-way or request-reply. The first operation is used to determine if the contract is one-way as the binding does not
* support mixing one-way and request-response operations on a service contract.
*
* @param chains the wire invocation chains
* @param uri thr service URI.
* @return true if the wire is one-way
*/
private boolean isOneWay(List chains, URI uri) {
if (chains.size() < 1) {
throw new AssertionError("Contract must have at least one operation: " + uri);
}
return chains.get(0).getPhysicalOperation().isOneWay();
}
private void addTransformer(List chains, ClassLoader loader) throws BrokerException {
for (InvocationChain chain : chains) {
try {
PhysicalOperationDefinition physicalOperation = chain.getPhysicalOperation();
List> targetTypes = createTypes(physicalOperation, loader);
Interceptor interceptor = interceptorFactory.createInterceptor(physicalOperation, TRANSPORT_TYPES, targetTypes, loader, loader);
chain.addInterceptor(new WrappingInterceptor());
chain.addInterceptor(interceptor);
} catch (InterceptorCreationException e) {
throw new BrokerException(e);
}
}
}
@SuppressWarnings({"unchecked"})
private List> createTypes(PhysicalOperationDefinition physicalOperation, ClassLoader loader) throws BrokerException {
try {
List> dataTypes = new ArrayList>();
if (physicalOperation.getSourceParameterTypes().isEmpty()) {
// no params
dataTypes.add(EMPTY_TYPE);
} else {
List> types = ParameterTypeHelper.loadSourceInParameterTypes(physicalOperation, loader);
for (Class> type : types) {
dataTypes.add(new JavaClass((type)));
}
}
return dataTypes;
} catch (ClassNotFoundException e) {
throw new BrokerException("Error transforming parameter", e);
}
}
private Interceptor createInterceptor(SenderHolder holder, int i) {
Sender sender = holder.getSender();
if (sender instanceof NonReliableRequestReplySender) {
return new RequestReplyInterceptor(i, (RequestReplySender) sender);
} else if (sender instanceof OneWaySender) {
return new OneWayInterceptor(i, (OneWaySender) sender);
} else {
throw new AssertionError("Unknown sender type: " + sender.getClass().getName());
}
}
private String createReceiverId(URI uri) {
if ("zmq".equals(uri.getScheme())) {
// callback ids are of the form zmq://
return uri.getAuthority();
}
return uri.getPath().substring(1) + "/" + uri.getFragment();
}
private class SenderHolder {
private Sender sender;
private List ids;
private SenderHolder(Sender sender) {
this.sender = sender;
ids = new ArrayList();
}
public Sender getSender() {
return sender;
}
public List getIds() {
return ids;
}
}
}