/*
* Copyright 2014 the original author or authors.
*
* 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 net.kuujo.vertigo.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.kuujo.vertigo.component.ComponentConfig;
import net.kuujo.vertigo.component.ComponentContext;
import net.kuujo.vertigo.component.InstanceContext;
import net.kuujo.vertigo.component.ModuleConfig;
import net.kuujo.vertigo.component.VerticleConfig;
import net.kuujo.vertigo.component.impl.DefaultInstanceContext;
import net.kuujo.vertigo.component.impl.DefaultModuleContext;
import net.kuujo.vertigo.component.impl.DefaultVerticleContext;
import net.kuujo.vertigo.hook.IOHook;
import net.kuujo.vertigo.io.connection.ConnectionConfig;
import net.kuujo.vertigo.io.connection.impl.DefaultConnectionContext;
import net.kuujo.vertigo.io.connection.impl.DefaultInputConnectionContext;
import net.kuujo.vertigo.io.connection.impl.DefaultOutputConnectionContext;
import net.kuujo.vertigo.io.impl.DefaultInputContext;
import net.kuujo.vertigo.io.impl.DefaultOutputContext;
import net.kuujo.vertigo.io.port.InputPortContext;
import net.kuujo.vertigo.io.port.OutputPortContext;
import net.kuujo.vertigo.io.port.impl.DefaultInputPortContext;
import net.kuujo.vertigo.io.port.impl.DefaultOutputPortContext;
import net.kuujo.vertigo.io.stream.impl.DefaultOutputStreamContext;
import net.kuujo.vertigo.network.MalformedNetworkException;
import net.kuujo.vertigo.network.NetworkConfig;
import net.kuujo.vertigo.network.NetworkContext;
import net.kuujo.vertigo.network.impl.DefaultNetworkContext;
/**
* A context builder.
*
* @author <a href="http://github.com/kuujo">Jordan Halterman</a>
*/
public final class ContextBuilder {
private static final String COMPONENT_ADDRESS_PATTERN = System.getProperty("vertigo.component.address", "%1$s.%2$s.%3$s");
/**
* Builds a network context from a network definition.
*
* @param network The network definition.
* @param cluster The cluster to which the network belongs.
* @return A new network context.
* @throws MalformedNetworkException If the network is malformed.
*/
public static NetworkContext buildContext(NetworkConfig network, String cluster) {
DefaultNetworkContext.Builder context = DefaultNetworkContext.Builder.newBuilder();
// Set basic network configuration options.
context.setName(network.getName());
context.setVersion(UUID.randomUUID().toString());
context.setAddress(String.format("%s.%s", cluster, network.getName()));
context.setConfig(network);
context.setCluster(cluster);
context.setStatusAddress(String.format("%s.%s.__status", cluster, network.getName()));
// Set up network components without inputs. Inputs are stored in a map so
// that they can be set up after all component instances have been set up.
Map<String, ComponentContext<?>> components = new HashMap<>();
for (ComponentConfig<?> component : network.getComponents()) {
if (component.getType().equals(ComponentConfig.Type.MODULE)) {
// Set up basic module configuration options.
DefaultModuleContext.Builder module = DefaultModuleContext.Builder.newBuilder();
module.setName(component.getName());
String address = String.format(COMPONENT_ADDRESS_PATTERN, cluster, network.getName(), component.getName());
module.setAddress(address);
module.setStatusAddress(String.format("%s.__status", address));
module.setModule(((ModuleConfig) component).getModule());
module.setConfig(component.getConfig());
module.setGroup(component.getGroup());
module.setHooks(component.getHooks());
// Set up module instances.
List<InstanceContext> instances = new ArrayList<>();
for (int i = 1; i <= component.getInstances(); i++) {
DefaultInstanceContext.Builder instance = DefaultInstanceContext.Builder.newBuilder();
instance.setAddress(String.format("%s-%d", address, i));
instance.setStatusAddress(String.format("%s-%d.__status", address, i));
instance.setNumber(i);
instance.setInput(DefaultInputContext.Builder.newBuilder().build());
instance.setOutput(DefaultOutputContext.Builder.newBuilder().build());
instances.add(instance.build());
}
module.setInstances(instances);
components.put(component.getName(), module.build());
} else {
// Set up basic verticle configuration options.
DefaultVerticleContext.Builder verticle = DefaultVerticleContext.Builder.newBuilder();
verticle.setName(component.getName());
String address = String.format(COMPONENT_ADDRESS_PATTERN, cluster, network.getName(), component.getName());
verticle.setAddress(address);
verticle.setStatusAddress(String.format("%s.__status", address));
verticle.setMain(((VerticleConfig) component).getMain());
verticle.setWorker(((VerticleConfig) component).isWorker());
verticle.setMultiThreaded(((VerticleConfig) component).isMultiThreaded());
verticle.setConfig(component.getConfig());
verticle.setGroup(component.getGroup());
verticle.setHooks(component.getHooks());
// Set up module instances.
List<InstanceContext> instances = new ArrayList<>();
for (int i = 1; i <= component.getInstances(); i++) {
DefaultInstanceContext.Builder instance = DefaultInstanceContext.Builder.newBuilder();
instance.setAddress(String.format("%s-%d", address, i));
instance.setStatusAddress(String.format("%s-%d.__status", address, i));
instance.setNumber(i);
instance.setInput(DefaultInputContext.Builder.newBuilder().build());
instance.setOutput(DefaultOutputContext.Builder.newBuilder().build());
instances.add(instance.build());
}
verticle.setInstances(instances);
components.put(component.getName(), verticle.build());
}
}
// Iterate through connections and create connection contexts.
// For each input connection, an internal input connection is created
// for each instance of the source component. Corresponding output connections
// are assigned to each output connection. In other words, each internal
// output connection can send to multiple addresses, but each internal input
// connection only listens on a single event bus address for messages from a
// single instance of the source component. This simplifies back pressure and
// resolving ordering issues in many-to-many component relationships.
for (ConnectionConfig connection : network.getConnections()) {
ComponentContext<?> source = components.get(connection.getSource().getComponent());
ComponentContext<?> target = components.get(connection.getTarget().getComponent());
// Only add connections if both components are currently in the network configuration.
// If a component is added to the configuration later then the context will need to
// be rebuilt.
if (source != null && target != null) {
for (InstanceContext sourceInstance : source.instances()) {
// Check if the port already exists on the source's output.
DefaultOutputPortContext.Builder output = null;
for (OutputPortContext port : sourceInstance.output().ports()) {
if (port.name().equals(connection.getSource().getPort())) {
output = DefaultOutputPortContext.Builder.newBuilder(port);
break;
}
}
// If the output port doesn't already exist then add it.
if (output == null) {
OutputPortContext port = DefaultOutputPortContext.Builder.newBuilder()
.setAddress(String.format("out:%s@%s.%s.%s[%d]", connection.getSource().getPort(), cluster, network.getName(), source.name(), sourceInstance.number()))
.setName(connection.getSource().getPort())
.build();
DefaultOutputContext.Builder.newBuilder(sourceInstance.output())
.addPort(port).build();
output = DefaultOutputPortContext.Builder.newBuilder(port);
}
// Set up an output stream from the output port.
DefaultOutputStreamContext.Builder outStream = DefaultOutputStreamContext.Builder.newBuilder();
outStream.setAddress(String.format("out:%s@%s.%s.%s[%d]->in:%s@%s.%s.%s[]", connection.getSource().getPort(), cluster, network.getName(), source.name(), sourceInstance.number(), connection.getTarget().getPort(), cluster, network.getName(), target.name()));
outStream.setSelector(connection.getSelector());
// For each target instance, add a unique input connection for the output.
for (InstanceContext targetInstance : target.instances()) {
// Check if the port already exists on the target's input.
DefaultInputPortContext.Builder input = null;
for (InputPortContext port : targetInstance.input().ports()) {
if (port.name().equals(connection.getTarget().getPort())) {
input = DefaultInputPortContext.Builder.newBuilder(port);
break;
}
}
// If the input port doesn't already exist then add it.
if (input == null) {
InputPortContext port = DefaultInputPortContext.Builder.newBuilder()
.setAddress(String.format("in:%s@%s.%s.%s[%d]", connection.getTarget().getPort(), cluster, network.getName(), target.name(), targetInstance.number()))
.setName(connection.getTarget().getPort())
.build();
DefaultInputContext.Builder.newBuilder(targetInstance.input())
.addPort(port).build();
input = DefaultInputPortContext.Builder.newBuilder(port);
}
// Add an input connection to the input port.
DefaultInputConnectionContext.Builder inConnection = DefaultInputConnectionContext.Builder.newBuilder();
String address = String.format("out:%s@%s.%s.%s[%d]->in:%s@%s.%s.%s[%d]", connection.getSource().getPort(), cluster, network.getName(), source.name(), sourceInstance.number(), connection.getTarget().getPort(), cluster, network.getName(), target.name(), targetInstance.number());
inConnection.setAddress(address);
inConnection.setSource(DefaultConnectionContext.DefaultSourceContext.Builder.newBuilder()
.setComponent(connection.getSource().getComponent())
.setPort(connection.getSource().getPort())
.setInstance(sourceInstance.number()).build());
inConnection.setTarget(DefaultConnectionContext.DefaultTargetContext.Builder.newBuilder()
.setComponent(connection.getTarget().getComponent())
.setPort(connection.getTarget().getPort())
.setInstance(targetInstance.number()).build());
// Add input level hooks to the input.
inConnection.setHooks(connection.getTarget().getHooks());
// Add connection level hooks to the input.
for (IOHook hook : connection.getHooks()) {
inConnection.addHook(hook);
}
// Add the connection to the target input port.
input.addConnection(inConnection.build()).build();
// Add the new output connection to the output stream. This creates a one-to-many
// relationship between output connections and input connections, and input
// connections maintain a many-to-one relationship with output connections.
DefaultOutputConnectionContext.Builder outConnection = DefaultOutputConnectionContext.Builder.newBuilder();
outConnection.setAddress(address);
outConnection.setSource(DefaultConnectionContext.DefaultSourceContext.Builder.newBuilder()
.setComponent(connection.getSource().getComponent())
.setPort(connection.getSource().getPort()).build());
outConnection.setTarget(DefaultConnectionContext.DefaultTargetContext.Builder.newBuilder()
.setComponent(connection.getTarget().getComponent())
.setPort(connection.getTarget().getPort()).build());
// Add output level hooks to the output.
outConnection.setHooks(connection.getSource().getHooks());
// Add connection level hooks to the output.
for (IOHook hook : connection.getHooks()) {
outConnection.addHook(hook);
}
outStream.addConnection(outConnection.build());
}
// Add the connection to the source instance's out port.
output.addStream(outStream.build()).build();
}
}
}
// Set the components on the network context and build the final context.
context.setComponents(components.values());
return context.build();
}
}