/*
* 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));
}
}
};
}
}
}