/* * 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.template; import java.net.URI; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.typesafe.config.Config; import gobblin.runtime.api.JobCatalogWithTemplates; import gobblin.runtime.api.JobTemplate; import gobblin.runtime.api.SpecNotFoundException; /** * A {@link JobTemplate} that supports inheriting other {@link JobTemplate}s. */ public abstract class InheritingJobTemplate implements JobTemplate { private final List<URI> superTemplateUris; private final JobCatalogWithTemplates catalog; private List<JobTemplate> superTemplates; private boolean resolved; public InheritingJobTemplate(List<URI> superTemplateUris, JobCatalogWithTemplates catalog) { this.superTemplateUris = superTemplateUris; this.catalog = catalog; this.resolved = false; } public InheritingJobTemplate(List<JobTemplate> superTemplates) { this.superTemplateUris = Lists.newArrayList(); this.catalog = null; this.superTemplates = superTemplates; this.resolved = true; } /** * Resolves a list of {@link URI}s to actual {@link JobTemplate}s. This pattern is necessary to detect loops in * inheritance and prevent them from causing a stack overflow. */ private synchronized void ensureTemplatesResolved() throws SpecNotFoundException, TemplateException{ if (this.resolved) { return; } Map<URI, JobTemplate> loadedTemplates = Maps.newHashMap(); loadedTemplates.put(getUri(), this); resolveTemplates(loadedTemplates); } private void resolveTemplates(Map<URI, JobTemplate> loadedTemplates) throws SpecNotFoundException, TemplateException { if (this.resolved) { return; } this.superTemplates = Lists.newArrayList(); for (URI uri : this.superTemplateUris) { if (!loadedTemplates.containsKey(uri)) { JobTemplate newTemplate = this.catalog.getTemplate(uri); loadedTemplates.put(uri, newTemplate); if (newTemplate instanceof InheritingJobTemplate) { ((InheritingJobTemplate) newTemplate).resolveTemplates(loadedTemplates); } } this.superTemplates.add(loadedTemplates.get(uri)); } this.resolved = true; } public Collection<JobTemplate> getSuperTemplates() throws SpecNotFoundException, TemplateException { ensureTemplatesResolved(); return ImmutableList.copyOf(this.superTemplates); } @Override public Config getRawTemplateConfig() throws SpecNotFoundException, TemplateException { ensureTemplatesResolved(); return getRawTemplateConfigHelper(Sets.<JobTemplate>newHashSet()); } private Config getRawTemplateConfigHelper(Set<JobTemplate> alreadyInheritedTemplates) throws SpecNotFoundException, TemplateException { Config rawTemplate = getLocalRawTemplate(); for (JobTemplate template : Lists.reverse(this.superTemplates)) { if (!alreadyInheritedTemplates.contains(template)) { alreadyInheritedTemplates.add(template); Config thisFallback = template instanceof InheritingJobTemplate ? ((InheritingJobTemplate) template).getRawTemplateConfigHelper(alreadyInheritedTemplates) : template.getRawTemplateConfig(); rawTemplate = rawTemplate.withFallback(thisFallback); } } return rawTemplate; } protected abstract Config getLocalRawTemplate(); @Override public Collection<String> getRequiredConfigList() throws SpecNotFoundException, TemplateException { ensureTemplatesResolved(); Set<String> allRequired = getRequiredConfigListHelper(Sets.<JobTemplate>newHashSet()); final Config rawConfig = getRawTemplateConfig(); Set<String> filteredRequired = Sets.filter(allRequired, new Predicate<String>() { @Override public boolean apply(String input) { return !rawConfig.hasPath(input); } }); return filteredRequired; } private Set<String> getRequiredConfigListHelper(Set<JobTemplate> alreadyLoadedTemplates) throws SpecNotFoundException, TemplateException { Set<String> requiredConfigs = Sets.newHashSet(getLocallyRequiredConfigList()); for (JobTemplate template : this.superTemplates) { if (!alreadyLoadedTemplates.contains(template)) { alreadyLoadedTemplates.add(template); requiredConfigs.addAll(template instanceof InheritingJobTemplate ? ((InheritingJobTemplate) template).getRequiredConfigListHelper(alreadyLoadedTemplates) : template.getRequiredConfigList()); } } return requiredConfigs; } protected abstract Collection<String> getLocallyRequiredConfigList(); @Override public Config getResolvedConfig(Config userConfig) throws SpecNotFoundException, TemplateException { ensureTemplatesResolved(); return getResolvedConfigHelper(userConfig, Sets.<JobTemplate>newHashSet()); } private Config getResolvedConfigHelper(Config userConfig, Set<JobTemplate> alreadyLoadedTemplates) throws SpecNotFoundException, TemplateException { Config config = getLocallyResolvedConfig(userConfig); for (JobTemplate template : Lists.reverse(this.superTemplates)) { if (!alreadyLoadedTemplates.contains(template)) { alreadyLoadedTemplates.add(template); Config fallback = template instanceof InheritingJobTemplate ? ((InheritingJobTemplate) template).getResolvedConfigHelper(config, alreadyLoadedTemplates) : template.getResolvedConfig(config); config = config.withFallback(fallback); } } return config; } protected abstract Config getLocallyResolvedConfig(Config userConfig) throws TemplateException; }