/* * 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.spec_catalog; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.Nonnull; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.util.concurrent.AbstractIdleService; import com.google.common.base.Preconditions; import com.typesafe.config.Config; import gobblin.configuration.ConfigurationKeys; import gobblin.instrumented.Instrumented; import gobblin.runtime.api.GobblinInstanceEnvironment; import gobblin.annotation.Alpha; import gobblin.metrics.MetricContext; import gobblin.metrics.Tag; import gobblin.runtime.api.FlowSpec; import gobblin.runtime.api.MutableSpecCatalog; import gobblin.runtime.api.Spec; import gobblin.runtime.api.SpecCatalog; import gobblin.runtime.api.SpecCatalogListener; import gobblin.runtime.api.SpecNotFoundException; import gobblin.runtime.api.SpecSerDe; import gobblin.runtime.api.SpecStore; import gobblin.runtime.spec_store.FSSpecStore; import gobblin.util.ClassAliasResolver; @Alpha public class FlowCatalog extends AbstractIdleService implements SpecCatalog, MutableSpecCatalog, SpecSerDe { public static final String DEFAULT_FLOWSPEC_STORE_CLASS = FSSpecStore.class.getCanonicalName(); protected final SpecCatalogListenersList listeners; protected final Logger log; protected final MetricContext metricContext; protected final FlowCatalog.StandardMetrics metrics; protected final SpecStore specStore; private final ClassAliasResolver<SpecStore> aliasResolver; public FlowCatalog(Config config) { this(config, Optional.<Logger>absent()); } public FlowCatalog(Config config, Optional<Logger> log) { this(config, log, Optional.<MetricContext>absent(), true); } public FlowCatalog(Config config, GobblinInstanceEnvironment env) { this(config, Optional.of(env.getLog()), Optional.of(env.getMetricContext()), env.isInstrumentationEnabled()); } public FlowCatalog(Config config, Optional<Logger> log, Optional<MetricContext> parentMetricContext, boolean instrumentationEnabled) { this.log = log.isPresent() ? log.get() : LoggerFactory.getLogger(getClass()); this.listeners = new SpecCatalogListenersList(log); if (instrumentationEnabled) { MetricContext realParentCtx = parentMetricContext.or(Instrumented.getMetricContext(new gobblin.configuration.State(), getClass())); this.metricContext = realParentCtx.childBuilder(FlowCatalog.class.getSimpleName()).build(); this.metrics = new StandardMetrics(this); } else { this.metricContext = null; this.metrics = null; } this.aliasResolver = new ClassAliasResolver<>(SpecStore.class); try { Config newConfig = config; if (config.hasPath(ConfigurationKeys.FLOWSPEC_STORE_DIR_KEY)) { newConfig = config.withValue(ConfigurationKeys.SPECSTORE_FS_DIR_KEY, config.getValue(ConfigurationKeys.FLOWSPEC_STORE_DIR_KEY)); } String specStoreClassName = DEFAULT_FLOWSPEC_STORE_CLASS; if (config.hasPath(ConfigurationKeys.FLOWSPEC_STORE_CLASS_KEY)) { specStoreClassName = config.getString(ConfigurationKeys.FLOWSPEC_STORE_CLASS_KEY); } this.log.info("Using audit sink class name/alias " + specStoreClassName); this.specStore = (SpecStore) ConstructorUtils.invokeConstructor(Class.forName(this.aliasResolver.resolve( specStoreClassName)), newConfig, this); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException | ClassNotFoundException e) { throw new RuntimeException(e); } } /*************************************************** /* Catalog init and shutdown handlers * /**************************************************/ @Override protected void startUp() throws Exception { notifyAllListeners(); } @Override protected void shutDown() throws Exception { this.listeners.close(); } /*************************************************** /* Catalog listeners * /**************************************************/ protected void notifyAllListeners() { for (Spec spec : getSpecs()) { this.listeners.onAddSpec(spec); } } @Override public void addListener(SpecCatalogListener specListener) { Preconditions.checkNotNull(specListener); this.listeners.addListener(specListener); if (state() == State.RUNNING) { for (Spec spec : getSpecs()) { SpecCatalogListener.AddSpecCallback addJobCallback = new SpecCatalogListener.AddSpecCallback(spec); this.listeners.callbackOneListener(addJobCallback, specListener); } } } @Override public void removeListener(SpecCatalogListener specCatalogListener) { this.listeners.removeListener(specCatalogListener); } @Override public void registerWeakSpecCatalogListener(SpecCatalogListener specCatalogListener) { this.listeners.registerWeakSpecCatalogListener(specCatalogListener); } /*************************************************** /* Catalog metrics * /**************************************************/ @Nonnull @Override public MetricContext getMetricContext() { return this.metricContext; } @Override public boolean isInstrumentationEnabled() { return null != this.metricContext; } @Override public List<Tag<?>> generateTags(gobblin.configuration.State state) { return Collections.emptyList(); } @Override public void switchMetricContext(List<Tag<?>> tags) { throw new UnsupportedOperationException(); } @Override public void switchMetricContext(MetricContext context) { throw new UnsupportedOperationException(); } @Override public StandardMetrics getMetrics() { return this.metrics; } /************************************************** /* Catalog core functionality * /**************************************************/ @Override public Collection<Spec> getSpecs() { try { return specStore.getSpecs(); } catch (IOException e) { throw new RuntimeException("Cannot retrieve Specs from Spec store", e); } } @Override public Spec getSpec(URI uri) throws SpecNotFoundException { try { return specStore.getSpec(uri); } catch (IOException e) { throw new RuntimeException("Cannot retrieve Spec from Spec store for URI: " + uri, e); } } @Override public void put(Spec spec) { try { Preconditions.checkState(state() == State.RUNNING, String.format("%s is not running.", this.getClass().getName())); Preconditions.checkNotNull(spec); log.info(String.format("Adding FlowSpec with URI: %s and Config: %s", spec.getUri(), ((FlowSpec) spec).getConfigAsProperties())); if (specStore.exists(spec.getUri())) { specStore.updateSpec(spec); this.listeners.onUpdateSpec(spec); } else { specStore.addSpec(spec); this.listeners.onAddSpec(spec); } } catch (IOException | SpecNotFoundException e) { throw new RuntimeException("Cannot add Spec to Spec store: " + spec, e); } } @Override public void remove(URI uri) { try { Preconditions.checkState(state() == State.RUNNING, String.format("%s is not running.", this.getClass().getName())); Preconditions.checkNotNull(uri); log.info(String.format("Removing FlowSpec with URI: %s", uri)); Spec spec = specStore.getSpec(uri); this.listeners.onDeleteSpec(spec.getUri(), spec.getVersion()); specStore.deleteSpec(uri); } catch (IOException | SpecNotFoundException e) { throw new RuntimeException("Cannot delete Spec from Spec store for URI: " + uri, e); } } @Override public byte[] serialize(Spec spec) { return SerializationUtils.serialize(spec); } @Override public Spec deserialize(byte[] spec) { return SerializationUtils.deserialize(spec); } }