/* * 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.broker; import java.io.Closeable; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import com.google.common.base.Predicate; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Service; import gobblin.broker.iface.ScopeType; import gobblin.broker.iface.SharedResourceFactory; import gobblin.broker.iface.SharedResourceKey; import gobblin.broker.iface.NoSuchScopeException; import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * A backing cache for shared resources used by {@link SharedResourcesBrokerImpl}. Stores created objects in a guava * cache keyed by factory, scope, and key. * @param <S> the {@link ScopeType} class for the scopes topology used in this tree of brokers. */ @Slf4j class DefaultBrokerCache<S extends ScopeType<S>> { private final Cache<RawJobBrokerKey, Object> sharedResourceCache; private final Cache<RawJobBrokerKey, ScopeWrapper<S>> autoScopeCache; public DefaultBrokerCache() { this.sharedResourceCache = CacheBuilder.newBuilder().build(); this.autoScopeCache = CacheBuilder.newBuilder().build(); } /** * The key for shared resources in the cache. */ @Data class RawJobBrokerKey { // Left if the key represents private final ScopeWrapper<S> scope; private final String factoryName; private final SharedResourceKey key; } /** * Get an object for the specified factory, key, and broker at the scope selected by the factory. {@link DefaultBrokerCache} * guarantees that calling this method from brokers with the same leaf scope will return the same object. */ @SuppressWarnings(value = "unchecked") <T, K extends SharedResourceKey> T getAutoScoped(final SharedResourceFactory<T, K, S> factory, final K key, final SharedResourcesBrokerImpl<S> broker) throws ExecutionException { // figure out auto scope RawJobBrokerKey autoscopeCacheKey = new RawJobBrokerKey(broker.getWrappedSelfScope(), factory.getName(), key); ScopeWrapper<S> selectedScope = this.autoScopeCache.get(autoscopeCacheKey, new Callable<ScopeWrapper<S>>() { @Override public ScopeWrapper<S> call() throws Exception { return broker.getWrappedScope(factory.getAutoScope(broker, broker.getConfigView(null, key, factory.getName()))); } }); // get actual object return getScoped(factory, key, selectedScope, broker); } /** * Get an object for the specified factory, key, scope, and broker. {@link DefaultBrokerCache} * guarantees that calling this method for the same factory, key, and scope will return the same object. */ @SuppressWarnings(value = "unchecked") <T, K extends SharedResourceKey> T getScoped(final SharedResourceFactory<T, K, S> factory, @Nonnull final K key, @Nonnull final ScopeWrapper<S> scope, final SharedResourcesBrokerImpl<S> broker) throws ExecutionException { RawJobBrokerKey fullKey = new RawJobBrokerKey(scope, factory.getName(), key); Object obj = this.sharedResourceCache.get(fullKey, new Callable<Object>() { @Override public Object call() throws Exception { return factory.createResource(broker.getScopedView(scope.getType()), broker.getConfigView(scope.getType(), key, factory.getName())); } }); if (obj instanceof ResourceCoordinate) { ResourceCoordinate<T, K, S> resourceCoordinate = (ResourceCoordinate<T, K, S>) obj; if (!SharedResourcesBrokerUtils.isScopeTypeAncestor((ScopeType) scope.getType(), ((ResourceCoordinate) obj).getScope())) { throw new RuntimeException(String.format("%s returned an invalid coordinate: scope %s is not an ancestor of %s.", factory.getName(), ((ResourceCoordinate) obj).getScope(), scope.getType())); } try { return getScoped(resourceCoordinate.getFactory(), resourceCoordinate.getKey(), broker.getWrappedScope(resourceCoordinate.getScope()), broker); } catch (NoSuchScopeException nsse) { throw new RuntimeException(String.format("%s returned an invalid coordinate: scope %s is not available.", factory.getName(), resourceCoordinate.getScope().name()), nsse); } } else if (obj instanceof ResourceInstance) { return ((ResourceInstance<T>) obj).getResource(); } else { throw new RuntimeException(String.format("Invalid response from %s: %s.", factory.getName(), obj.getClass())); } } <T, K extends SharedResourceKey> void put(final SharedResourceFactory<T, K, S> factory, @Nonnull final K key, @Nonnull final ScopeWrapper<S> scope, T instance) { RawJobBrokerKey fullKey = new RawJobBrokerKey(scope, factory.getName(), key); this.sharedResourceCache.put(fullKey, instance); } /** * Invalidate all objects at scopes which are descendant of the input scope. Any such invalidated object that is a * {@link Closeable} will be closed, and any such object which is a {@link Service} will be shutdown. * @throws IOException */ public void close(ScopeWrapper<S> scope) throws IOException { List<Service> awaitShutdown = Lists.newArrayList(); for (Map.Entry<RawJobBrokerKey, Object> entry : Maps.filterKeys(this.sharedResourceCache.asMap(), new ScopeIsAncestorFilter(scope)).entrySet()) { this.sharedResourceCache.invalidate(entry.getKey()); if (entry.getValue() instanceof ResourceInstance) { Object obj = ((ResourceInstance) entry.getValue()).getResource(); if (obj instanceof Service) { ((Service) obj).stopAsync(); awaitShutdown.add((Service) obj); } else if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException ioe) { log.error("Failed to close {}.", obj); } } } } for (Service service : awaitShutdown) { try { service.awaitTerminated(10, TimeUnit.SECONDS); } catch (TimeoutException te) { log.error("Failed to shutdown {}.", service); } } } /** * Filter {@link RawJobBrokerKey} that are not descendants of the input {@link ScopeWrapper}. */ @AllArgsConstructor private class ScopeIsAncestorFilter implements Predicate<RawJobBrokerKey> { private final ScopeWrapper<S> scope; @Override public boolean apply(RawJobBrokerKey input) { if (this.scope == null) { return true; } if (input.getScope() == null) { return false; } return SharedResourcesBrokerUtils.isScopeAncestor(input.getScope(), this.scope); } } }