/* * Copyright © 2016 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.cdap.app.guice; import co.cask.cdap.api.Admin; import co.cask.cdap.api.Transactional; import co.cask.cdap.api.TxRunnable; import co.cask.cdap.api.data.DatasetContext; import co.cask.cdap.api.metrics.MetricsCollectionService; import co.cask.cdap.data.dataset.SystemDatasetInstantiator; import co.cask.cdap.data2.dataset2.DatasetFramework; import co.cask.cdap.data2.dataset2.DynamicDatasetCache; import co.cask.cdap.data2.dataset2.MultiThreadDatasetCache; import co.cask.cdap.internal.app.runtime.DefaultAdmin; import co.cask.cdap.proto.id.NamespaceId; import co.cask.cdap.security.authorization.AuthorizationContextFactory; import co.cask.cdap.security.authorization.AuthorizerInstantiatorService; import co.cask.cdap.security.authorization.DefaultAuthorizationContext; import co.cask.cdap.security.spi.authorization.AuthorizationContext; import co.cask.cdap.security.spi.authorization.Authorizer; import co.cask.tephra.TransactionContext; import co.cask.tephra.TransactionFailureException; import co.cask.tephra.TransactionSystemClient; import com.google.common.collect.ImmutableMap; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.PrivateModule; import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.Singleton; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.FactoryModuleBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Properties; /** * {@link PrivateModule} for authorization classes. This module is necessary and must be in app-fabric because classes * like {@link DatasetFramework}, {@link DynamicDatasetCache}, {@link DefaultAdmin} are not available in cdap-security. * * This module is part of the injector created in StandaloneMain and MasterServiceMain, which makes it available to * services. The requirements for this module are: * 1. This module is used for creating and exposing {@link AuthorizerInstantiatorService}. * 2. The {@link AuthorizerInstantiatorService} needs a {@link DefaultAuthorizationContext}. * 3. The {@link DefaultAuthorizationContext} needs a {@link DatasetContext}, a {@link Admin} and a * {@link Transactional}. * * These requirements are fulfilled by: * 1. Constructing a {@link Singleton} {@link MultiThreadDatasetCache} by injecting a {@link DatasetFramework}, a * {@link TransactionSystemClient} and a {@link MetricsCollectionService}. This {@link MultiThreadDatasetCache} is * created for datasets in the {@link NamespaceId#SYSTEM}, since the datasets that {@link Authorizer} extensions need * are created in the system namespace. * 2. Binding the {@link DatasetContext} to the {@link MultiThreadDatasetCache} created above. * 3. Using the {@link MultiThreadDatasetCache} to create a {@link TransactionContext} for providing the * {@link Transactional}. * 4. Binding a {@link DefaultAdmin} by injecting {@link DatasetFramework}. This {@link DefaultAdmin} is also created * for the {@link NamespaceId#SYSTEM}. * 5. Using the bound {@link DatasetContext}, {@link Admin} and {@link Transactional} to provide the injections for * {@link DefaultAuthorizationContext}, which is provided using a {@link Guice} {@link FactoryModuleBuilder} to * construct a {@link AuthorizationContextFactory}. * 6. Only exposing a {@link Singleton} binding to {@link AuthorizerInstantiatorService} from this module. The * {@link AuthorizerInstantiatorService} can just {@link Inject} the {@link AuthorizationContextFactory} and call * {@link AuthorizationContextFactory#create(Properties)} using an {@link Assisted} {@link Properties} object. */ public class AuthorizationModule extends PrivateModule { @Override protected void configure() { bind(DynamicDatasetCache.class).toProvider(DynamicDatasetCacheProvider.class).in(Scopes.SINGLETON); bind(DatasetContext.class).to(DynamicDatasetCache.class).in(Scopes.SINGLETON); bind(Admin.class).toProvider(AdminProvider.class); bind(Transactional.class).toProvider(TransactionalProvider.class); install( new FactoryModuleBuilder() .implement(AuthorizationContext.class, DefaultAuthorizationContext.class) .build(AuthorizationContextFactory.class) ); bind(AuthorizerInstantiatorService.class).in(Scopes.SINGLETON); expose(AuthorizerInstantiatorService.class); } @Singleton private static final class DynamicDatasetCacheProvider implements Provider<DynamicDatasetCache> { private final DatasetFramework dsFramework; private final TransactionSystemClient txClient; private final MetricsCollectionService metricsCollectionService; @Inject private DynamicDatasetCacheProvider(DatasetFramework dsFramework, TransactionSystemClient txClient, MetricsCollectionService metricsCollectionService) { this.dsFramework = dsFramework; this.txClient = txClient; this.metricsCollectionService = metricsCollectionService; } @Override public DynamicDatasetCache get() { SystemDatasetInstantiator dsInstantiator = new SystemDatasetInstantiator(dsFramework, null, null); return new MultiThreadDatasetCache( dsInstantiator, txClient, NamespaceId.SYSTEM, ImmutableMap.<String, String>of(), metricsCollectionService.getContext(ImmutableMap.<String, String>of()), ImmutableMap.<String, Map<String, String>>of() ); } } @Singleton private static final class AdminProvider implements Provider<Admin> { private final DatasetFramework dsFramework; @Inject private AdminProvider(DatasetFramework dsFramework) { this.dsFramework = dsFramework; } @Override public Admin get() { return new DefaultAdmin(dsFramework, NamespaceId.SYSTEM); } } @Singleton private static final class TransactionalProvider implements Provider<Transactional> { private static final Logger LOG = LoggerFactory.getLogger(TransactionalProvider.class); private final DynamicDatasetCache datasetCache; @Inject private TransactionalProvider(DynamicDatasetCache datasetCache) { this.datasetCache = datasetCache; } @Override public Transactional get() { return new Transactional() { @Override public void execute(TxRunnable runnable) throws TransactionFailureException { TransactionContext transactionContext = datasetCache.newTransactionContext(); transactionContext.start(); try { runnable.run(datasetCache); transactionContext.finish(); } catch (TransactionFailureException e) { LOG.error("Exception while executing runnable inside a transaction. Aborting transaction.", e); transactionContext.abort(e); } catch (Throwable t) { LOG.error("Exception while executing runnable inside a transaction. Aborting transaction.", t); transactionContext.abort(new TransactionFailureException("Exception raised from TxRunnable.run()", t)); } } }; } } }