/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 gobblin.runtime.api;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
import javax.annotation.concurrent.NotThreadSafe;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import gobblin.annotation.Alpha;
import gobblin.configuration.ConfigurationKeys;
import gobblin.runtime.spec_executorInstance.InMemorySpecExecutorInstanceProducer;
import gobblin.util.ClassAliasResolver;
import gobblin.util.ConfigUtils;
/**
* Data model representation that describes a topology ie. a {@link SpecExecutorInstance} and its
* capabilities tuple .
*
*/
@Alpha
@Data
@AllArgsConstructor
@NotThreadSafe
public class TopologySpec implements Configurable, Spec {
public static final String DEFAULT_SPEC_EXECUTOR_INSTANCE_PRODUCER = InMemorySpecExecutorInstanceProducer.class.getCanonicalName();
public static final String SPEC_EXECUTOR_INSTANCE_PRODUCER_KEY = "specExecutorInstanceProducer.class";
private static final long serialVersionUID = 6106269076155338046L;
/** An URI identifying the topology. */
final URI uri;
/** The implementation-defined version of this spec. */
final String version;
/** Human-readable description of the topology spec */
final String description;
/** Topology config as a typesafe config object*/
@SuppressWarnings(justification="No bug", value="SE_BAD_FIELD")
final Config config;
/** Topology config as a properties collection for backwards compatibility */
// Note that this property is not strictly necessary as it can be generated from the typesafe
// config. We use it as a cache until typesafe config is more widely adopted in Gobblin.
final Properties configAsProperties;
/** Underlying executor instance such as Gobblin cluster or Azkaban */
@SuppressWarnings(justification="Initialization handled by getter", value="SE_TRANSIENT_FIELD_NOT_RESTORED")
transient SpecExecutorInstanceProducer specExecutorInstanceProducer;
public SpecExecutorInstanceProducer getSpecExecutorInstanceProducer() {
if (null == specExecutorInstanceProducer) {
String specExecutorInstanceProducerClass = DEFAULT_SPEC_EXECUTOR_INSTANCE_PRODUCER;
if (config.hasPath(SPEC_EXECUTOR_INSTANCE_PRODUCER_KEY)) {
specExecutorInstanceProducerClass = config.getString(SPEC_EXECUTOR_INSTANCE_PRODUCER_KEY);
}
try {
ClassAliasResolver<SpecExecutorInstanceProducer> _aliasResolver =
new ClassAliasResolver<>(SpecExecutorInstanceProducer.class);
specExecutorInstanceProducer = (SpecExecutorInstanceProducer) ConstructorUtils
.invokeConstructor(Class.forName(_aliasResolver
.resolve(specExecutorInstanceProducerClass)), config);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException
| ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return specExecutorInstanceProducer;
}
public static TopologySpec.Builder builder(URI topologySpecUri) {
return new TopologySpec.Builder(topologySpecUri);
}
public static TopologySpec.Builder builder(String topologySpecUri) {
return new TopologySpec.Builder(topologySpecUri);
}
public static TopologySpec.Builder builder() {
return new TopologySpec.Builder();
}
/** Creates a builder for the TopologySpec based on values in a topology properties config. */
public static TopologySpec.Builder builder(URI catalogURI, Properties topologyProps) {
String name = topologyProps.getProperty(ConfigurationKeys.TOPOLOGY_NAME_KEY);
String group = topologyProps.getProperty(ConfigurationKeys.TOPOLOGY_GROUP_KEY, "default");
try {
URI topologyURI = new URI(catalogURI.getScheme(), catalogURI.getAuthority(),
"/" + group + "/" + name, null);
TopologySpec.Builder builder = new TopologySpec.Builder(topologyURI).withConfigAsProperties(topologyProps);
String descr = topologyProps.getProperty(ConfigurationKeys.TOPOLOGY_DESCRIPTION_KEY, null);
if (null != descr) {
builder = builder.withDescription(descr);
}
return builder;
} catch (URISyntaxException e) {
throw new RuntimeException("Unable to create a TopologySpec URI: " + e, e);
}
}
public String toShortString() {
return getUri().toString() + "/" + getVersion();
}
public String toLongString() {
return getUri().toString() + "/" + getVersion() + "[" + getDescription() + "]";
}
@Override
public String toString() {
return toShortString();
}
/**
* Builder for {@link TopologySpec}s.
* <p> Defaults/conventions:
* <ul>
* <li> Default topologyCatalogURI is {@link #DEFAULT_TOPOLOGY_CATALOG_SCHEME}:
* <li> Convention for TopologySpec URI: <topologyCatalogURI>/config.get({@link ConfigurationKeys#TOPOLOGY_GROUP_KEY})/config.get({@link ConfigurationKeys#TOPOLOGY_NAME_KEY})
* <li> Convention for Description: config.get({@link ConfigurationKeys#TOPOLOGY_DESCRIPTION_KEY})
* <li> Default version: 1
* </ul>
*/
public static class Builder {
public static final String DEFAULT_TOPOLOGY_CATALOG_SCHEME = "gobblin-topology";
@VisibleForTesting
private Optional<Config> config = Optional.absent();
private Optional<Properties> configAsProperties = Optional.absent();
private Optional<URI> uri;
private String version = "1";
private Optional<String> description = Optional.absent();
private Optional<URI> topologyCatalogURI = Optional.absent();
private Optional<SpecExecutorInstanceProducer> specExecutorInstanceProducer = Optional.absent();
public Builder(URI topologySpecUri) {
Preconditions.checkNotNull(topologySpecUri);
this.uri = Optional.of(topologySpecUri);
}
public Builder(String topologySpecUri) {
Preconditions.checkNotNull(topologySpecUri);
Preconditions.checkNotNull(topologySpecUri);
try {
this.uri = Optional.of(new URI(topologySpecUri));
}
catch (URISyntaxException e) {
throw new RuntimeException("Invalid TopologySpec config: " + e, e);
}
}
public Builder() {
this.uri = Optional.absent();
}
public TopologySpec build() {
Preconditions.checkNotNull(this.uri);
Preconditions.checkNotNull(this.version);
return new TopologySpec(getURI(), getVersion(), getDescription(), getConfig(),
getConfigAsProperties(), getSpecExceutorInstanceProducer());
}
/** The scheme and authority of the topology catalog URI are used to generate TopologySpec URIs from
* topology configs. */
public TopologySpec.Builder withTopologyCatalogURI(URI topologyCatalogURI) {
this.topologyCatalogURI = Optional.of(topologyCatalogURI);
return this;
}
public TopologySpec.Builder withTopologyCatalogURI(String topologyCatalogURI) {
try {
this.topologyCatalogURI = Optional.of(new URI(topologyCatalogURI));
} catch (URISyntaxException e) {
throw new RuntimeException("Unable to set topology catalog URI: " + e, e);
}
return this;
}
public URI getDefaultTopologyCatalogURI() {
try {
return new URI(DEFAULT_TOPOLOGY_CATALOG_SCHEME, null, "/", null, null);
} catch (URISyntaxException e) {
// should not happen
throw new Error("Unexpected exception: " + e, e);
}
}
public URI getTopologyCatalogURI() {
if (! this.topologyCatalogURI.isPresent()) {
this.topologyCatalogURI = Optional.of(getDefaultTopologyCatalogURI());
}
return this.topologyCatalogURI.get();
}
public URI getDefaultURI() {
URI topologyCatalogURI = getTopologyCatalogURI();
Config topologyCfg = getConfig();
String name = topologyCfg.hasPath(ConfigurationKeys.TOPOLOGY_NAME_KEY) ?
topologyCfg.getString(ConfigurationKeys.TOPOLOGY_NAME_KEY) :
"default";
String group = topologyCfg.hasPath(ConfigurationKeys.TOPOLOGY_GROUP_KEY) ?
topologyCfg.getString(ConfigurationKeys.TOPOLOGY_GROUP_KEY) :
"default";
try {
return new URI(topologyCatalogURI.getScheme(), topologyCatalogURI.getAuthority(),
"/" + group + "/" + name, null, null);
} catch (URISyntaxException e) {
throw new RuntimeException("Unable to create default TopologySpec URI:" + e, e);
}
}
public URI getURI() {
if (! this.uri.isPresent()) {
this.uri = Optional.of(getDefaultURI());
}
return this.uri.get();
}
public TopologySpec.Builder withVersion(String version) {
Preconditions.checkNotNull(version);
this.version = version;
return this;
}
public String getVersion() {
return this.version;
}
public TopologySpec.Builder withDescription(String topologyDescription) {
Preconditions.checkNotNull(topologyDescription);
this.description = Optional.of(topologyDescription);
return this;
}
public String getDefaultDescription() {
Config topologyConf = getConfig();
return topologyConf.hasPath(ConfigurationKeys.TOPOLOGY_DESCRIPTION_KEY) ?
topologyConf.getString(ConfigurationKeys.TOPOLOGY_DESCRIPTION_KEY) :
"Gobblin topology " + getURI();
}
public String getDescription() {
if (! this.description.isPresent()) {
this.description = Optional.of(getDefaultDescription());
}
return this.description.get();
}
public Config getDefaultConfig() {
return ConfigFactory.empty();
}
public Config getConfig() {
if (!this.config.isPresent()) {
this.config = this.configAsProperties.isPresent() ?
Optional.of(ConfigUtils.propertiesToTypedConfig(this.configAsProperties.get(),
Optional.<String>absent())) :
Optional.of(getDefaultConfig());
}
return this.config.get();
}
public TopologySpec.Builder withConfig(Config topologyConfig) {
Preconditions.checkNotNull(topologyConfig);
this.config = Optional.of(topologyConfig);
return this;
}
public Properties getConfigAsProperties() {
if (!this.configAsProperties.isPresent()) {
this.configAsProperties = Optional.of(ConfigUtils.configToProperties(this.config.get()));
}
return this.configAsProperties.get();
}
public TopologySpec.Builder withConfigAsProperties(Properties topologyConfig) {
Preconditions.checkNotNull(topologyConfig);
this.configAsProperties = Optional.of(topologyConfig);
return this;
}
public SpecExecutorInstanceProducer getSpecExceutorInstanceProducer() {
if (!this.specExecutorInstanceProducer.isPresent()) {
// TODO: Try to init SpecExecutorInstanceProducer from config if not initialized via builder.
throw new RuntimeException("SpecExecutorInstanceProducer not initialized.");
}
return this.specExecutorInstanceProducer.get();
}
public TopologySpec.Builder withSpecExecutorInstanceProducer(SpecExecutorInstanceProducer specExecutorInstanceProducer) {
Preconditions.checkNotNull(specExecutorInstanceProducer);
this.specExecutorInstanceProducer = Optional.of(specExecutorInstanceProducer);
return this;
}
}
/**
* get the private uri as the primary key for this object.
* @return
*/
public URI getUri() {
return this.uri;
}
}