/*
* Copyright © 2014 Cask Data, Inc.
*
* 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 co.cask.tigon.api.flow;
import co.cask.tigon.api.ProgramSpecification;
import co.cask.tigon.api.flow.flowlet.Flowlet;
import co.cask.tigon.internal.UserErrors;
import co.cask.tigon.internal.UserMessages;
import co.cask.tigon.internal.flow.DefaultFlowSpecification;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
/**
* This class provides the specification of a Flow. Instances of this class should be created through
* the {@link Builder} class by invoking the {@link Builder#with()} method.
*
* <p>
* Example FlowSpecification:
*
* <pre>
* <code>
* public class PurchaseFlow implements Flow {
* {@literal @}Override
* public FlowSpecification configure() {
* return FlowSpecification.Builder.with()
* .setName("PurchaseFlow")
* .setDescription("Reads user and purchase information and stores in dataset")
* .withFlowlets()
* .add("reader", new PurchaseReader())
* .add("collector", new PurchaseStore())
* .connect()
* .from("reader").to("collector")
* .build();
* }
* }
* </code>
* </pre>
*
* See the <i>Cask DAP Developer Guide</i> and the CDAP instance example applications.
*
* @see co.cask.tigon.api.flow.flowlet.Flowlet Flowlet
*
*/
public interface FlowSpecification extends ProgramSpecification {
/**
* @return Immutable Map from flowlet name to {@link FlowletDefinition}.
*/
Map<String, FlowletDefinition> getFlowlets();
/**
* @return Immutable list of {@link FlowletConnection}s.
*/
List<FlowletConnection> getConnections();
/**
* Defines a builder for building connections or topology for a flow.
*/
static final class Builder {
private String name;
private String description;
private final Map<String, FlowletDefinition> flowlets = Maps.newHashMap();
private final List<FlowletConnection> connections = Lists.newArrayList();
/**
* Creates a {@link Builder} for building instance of {@link FlowSpecification}.
*
* @return a new builder instance.
*/
public static NameSetter with() {
return new Builder().new NameSetter();
}
/**
* Class for setting flow name.
*/
public final class NameSetter {
/**
* Sets the name of the Flow.
* @param name of the flow.
* @return An instance of {@link DescriptionSetter}
*/
public DescriptionSetter setName(String name) {
Preconditions.checkArgument(name != null, UserMessages.getMessage(UserErrors.FLOW_SPEC_NAME));
Builder.this.name = name;
return new DescriptionSetter();
}
}
/**
* Defines a class for defining the flow description.
*/
public final class DescriptionSetter {
/**
* Sets the description for the flow.
* @param description of the flow.
* @return A instance of {@link AfterDescription}
*/
public AfterDescription setDescription(String description) {
Preconditions.checkArgument(description != null, UserMessages.getMessage(UserErrors.FLOW_SPEC_DESC));
Builder.this.description = description;
return new AfterDescription();
}
}
/**
* Defines a class that represents what needs to happen after a description
* has been added.
*/
public final class AfterDescription {
/**
* @return An instance of {@link FlowletAdder} for adding flowlets to specification.
*/
public FlowletAdder withFlowlets() {
return new MoreFlowlet();
}
}
/**
* FlowletAdder is responsible for capturing the information of a Flowlet during the
* specification creation.
*/
public interface FlowletAdder {
/**
* Add a flowlet to the flow.
* @param flowlet {@link Flowlet} instance to be added to flow.
* @return An instance of {@link MoreFlowlet} for adding more flowlets
*/
MoreFlowlet add(Flowlet flowlet);
/**
* Add a flowlet to the flow with the minimum number of instances of the flowlet to start with.
* @param flowlet {@link Flowlet} instance to be added to flow.
* @param instances Number of instances for the flowlet
* @return An instance of {@link MoreFlowlet} for adding more flowlets
*/
MoreFlowlet add(Flowlet flowlet, int instances);
/**
* Add a flowlet to flow with the specified name. The specified name overrides the one
* in {@link co.cask.tigon.api.flow.flowlet.FlowletSpecification#getName() FlowletSpecification.getName()}
* returned by {@link Flowlet#configure()}.
* @param name Name of the flowlet
* @param flowlet {@link Flowlet} instance to be added to flow.
* @return An instance of {@link MoreFlowlet} for adding more flowlets.
*/
MoreFlowlet add(String name, Flowlet flowlet);
/**
* Add a flowlet to flow with the specified name with minimum number of instances to start with.
* The name specified overrides the one
* in {@link co.cask.tigon.api.flow.flowlet.FlowletSpecification#getName() FlowletSpecification.getName()}
* returned by {@link Flowlet#configure()}.
* @param name Name of the flowlet
* @param flowlet {@link Flowlet} instance to be added to flow.
* @param instances Number of instances for the flowlet
* @return An instance of {@link MoreFlowlet} for adding more flowlets.
*/
MoreFlowlet add(String name, Flowlet flowlet, int instances);
}
/**
* This class allows more flowlets to be defined. This is part of a controlled builder.
*/
public final class MoreFlowlet implements FlowletAdder {
@Override
public MoreFlowlet add(Flowlet flowlet) {
return add(flowlet, 1);
}
@Override
public MoreFlowlet add(final Flowlet flowlet, int instances) {
return add(null, flowlet, instances);
}
@Override
public MoreFlowlet add(String name, Flowlet flowlet) {
return add(name, flowlet, 1);
}
@Override
public MoreFlowlet add(String name, Flowlet flowlet, int instances) {
Preconditions.checkArgument(flowlet != null, UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NULL));
FlowletDefinition flowletDef = new FlowletDefinition(name, flowlet, instances);
String flowletName = flowletDef.getFlowletSpec().getName();
Preconditions.checkArgument(instances > 0, String.format(UserMessages.getMessage(UserErrors.INVALID_INSTANCES),
flowletName, instances));
Preconditions.checkArgument(!flowlets.containsKey(flowletName),
UserMessages.getMessage(UserErrors.INVALID_FLOWLET_EXISTS), flowletName);
flowlets.put(flowletName, flowletDef);
return this;
}
/**
* Defines a connection between two flowlets.
* @return An instance of {@link ConnectFrom}
*/
public ConnectFrom connect() {
return new Connector();
}
}
/**
* Defines the starting flowlet for a connection.
*/
public interface ConnectFrom {
/**
* Defines the flowlet that is at the beginning of the connection.
* @param flowlet that is at the beginning of connection.
* @return An instance of {@link ConnectTo} specifying the flowlet it will connect to.
*/
ConnectTo from(Flowlet flowlet);
/**
* Defines the flowlet that is at the beginning of the connection by the flowlet name.
* @param flowlet Name of the flowlet.
* @return And instance of {@link ConnectTo} specifying the flowlet it will connect to.
*/
ConnectTo from(String flowlet);
}
/**
* Class defining the ConnectTo interface for a connection.
*/
public interface ConnectTo {
/**
* Defines the flowlet that the connection is connecting to.
* @param flowlet the connection connects to.
* @return A instance of {@link MoreConnect} to define more connections of flowlets in a flow.
*/
MoreConnect to(Flowlet flowlet);
/**
* Defines the flowlet that connection is connecting to by the flowlet name.
* @param flowlet Name of the flowlet the connection connects to.
* @return A instance of {@link MoreConnect} to define more connections of flowlets in a flow.
*/
MoreConnect to(String flowlet);
}
/**
* Interface that defines the building of FlowSpecification.
*/
public interface MoreConnect extends ConnectFrom {
/**
* Constructs a {@link FlowSpecification}.
* @return An instance of {@link FlowSpecification}
*/
FlowSpecification build();
}
/**
* Class that defines the connection between two flowlets.
*/
public final class Connector implements ConnectFrom, ConnectTo, MoreConnect {
private FlowletDefinition fromFlowlet;
@Override
public ConnectTo from(Flowlet flowlet) {
Preconditions.checkArgument(flowlet != null, UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NULL));
return from(flowlet.configure().getName());
}
@Override
public ConnectTo from(String flowlet) {
Preconditions.checkArgument(flowlets.containsKey(flowlet),
UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NAME), flowlet);
fromFlowlet = flowlets.get(flowlet);
return this;
}
@Override
public MoreConnect to(Flowlet flowlet) {
Preconditions.checkArgument(flowlet != null, UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NULL));
return to(flowlet.configure().getName());
}
@Override
public MoreConnect to(String flowlet) {
Preconditions.checkArgument(flowlet != null, UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NULL));
Preconditions.checkArgument(flowlets.containsKey(flowlet),
UserMessages.getMessage(UserErrors.INVALID_FLOWLET_NAME), flowlet);
FlowletConnection.Type sourceType;
String sourceName;
sourceType = FlowletConnection.Type.FLOWLET;
sourceName = fromFlowlet.getFlowletSpec().getName();
connections.add(new FlowletConnection(sourceType, sourceName, flowlet));
return this;
}
@Override
public FlowSpecification build() {
return new DefaultFlowSpecification(name, description, flowlets, connections);
}
}
private Builder() {
}
}
}