package ch.unibe.scg.cells;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.util.Types.newParameterizedType;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import javax.inject.Inject;
import javax.inject.Provider;
import ch.unibe.scg.cells.CounterModule.CounterName;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.PrivateModule;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Types;
/**
* An API that helps configure Cells jobs. Helps configure tables, sources, sinks, and counters.
* To configure your cells job, just subclass CellsModule, and use helper methods.
*/
public abstract class CellsModule extends AbstractModule {
private static class SourceProvider<T> implements Provider<Source<T>> {
final private Provider<CellSource<Void>> src;
final private Provider<Codec<T>> codec;
@Inject
SourceProvider(Provider<CellSource<Void>> src, Provider<Codec<T>> codec) {
this.src = src;
this.codec = codec;
}
@Override
public Source<T> get() {
return Cells.decodeSource((CellSource<T>) src.get(), codec.get());
}
}
private static class SinkProvider<T> implements Provider<Sink<T>> {
final private Provider<CellSink<Void>> sink;
final private Provider<Codec<T>> codec;
@Inject
SinkProvider(Provider<CellSink<Void>> sink, Provider<Codec<T>> codec) {
this.codec = codec;
this.sink = sink;
}
@Override
public Sink<T> get() {
return Cells.encodeSink(((CellSink<T>) sink.get()), codec.get());
}
}
private static class LookupTableProvider<T> implements Provider<LookupTable<T>> {
final private Provider<CellLookupTable<Void>> rawCellTable;
final private Provider<Codec<T>> codec;
@Inject
LookupTableProvider(Provider<CellLookupTable<Void>> rawCellTable, Provider<Codec<T>> codec) {
this.rawCellTable = rawCellTable;
this.codec = codec;
}
@Override
public LookupTable<T> get() {
return Cells.decodedTable((CellLookupTable<T>) rawCellTable.get(), codec.get());
}
}
/**
* Example:
* <pre>
* {@code
Module tab = new CellsModule() {
@Override
protected void configure() {
installTable(
In.class,
new TypeLiteral<Act>() {},
ActCodec.class,
new HBaseStorage(), new HBaseTableModule(TABLE_NAME_IN, fam));
installTable(
Eff.class,
new TypeLiteral<WordCount>() {},
WordCountCodec.class,
new HBaseStorage(), new HBaseTableModule(TABLE_NAME_EFF, fam));
}
};
*}
*</pre>
*
* <p>
* Once you've got that, you can simply ask for the proper Sink, Source, or LookupTable
* to be injected. Example:
* <pre> {@code
* @Inject
* MyConstructor(@In LookupTable<Act> actLookup, @Eff Sink<WordCount>) {
* …
* }
* } </pre>
*
* @param tableModule The information needed to initialize this specific table.
* That's things like table name and column family, for the case of HBase tables.
* In memory tables don't really need this, so for in memory tables, just pass an empty TableModule.
* If you don't know what you want, pass in an HBaseTableModule. Then, you can create both
* in memory tables, and HBase tables.
*
*/
@SuppressWarnings("javadoc") // Yea, fuck that. For javadoc to be happy, I have to escape "@", "{", "}", "<", ">".
// That makes copy-pasting from code impossible.
protected final <T> void installTable(
final Class<? extends Annotation> annotation, final TypeLiteral<T> lit,
final Class<? extends Codec<T>> codec, final StorageModule storageModule,
final TableModule tableModule) {
checkNotNull(annotation);
checkNotNull(lit);
checkNotNull(codec);
checkNotNull(storageModule);
install(new PrivateModule() {
@Override protected void configure() {
install(storageModule);
install(tableModule);
bind((Key<Codec<T>>) Key.get(Types.newParameterizedType(Codec.class, lit.getType()))).to(codec);
bind((Key<Source<T>>) Key.get(newParameterizedType(Source.class, lit.getType()))).toProvider(
(Key<Provider<Source<T>>>) Key.get(newParameterizedTypeWithOwner(CellsModule.class,
SourceProvider.class, lit.getType())));
bind((Key<Sink<T>>) Key.get(newParameterizedType(Sink.class, lit.getType()))).toProvider(
(Key<Provider<Sink<T>>>) Key.get(newParameterizedTypeWithOwner(CellsModule.class, SinkProvider.class,
lit.getType())));
bind((Key<LookupTable<T>>) Key.get(newParameterizedType(LookupTable.class, lit.getType()))).toProvider(
(Key<Provider<LookupTable<T>>>) Key.get(newParameterizedTypeWithOwner(CellsModule.class,
LookupTableProvider.class, lit.getType())));
ParameterizedType src = Types.newParameterizedType(Source.class, lit.getType());
Key<Source<T>> exposedSrc = (Key<Source<T>>) Key.get(src, annotation);
bind(exposedSrc).to((Key<? extends Source<T>>) Key.get(src));
expose(exposedSrc);
ParameterizedType sink = Types.newParameterizedType(Sink.class, lit.getType());
Key<Sink<T>> exposedSink = (Key<Sink<T>>) Key.get(sink, annotation);
bind(exposedSink).to((Key<? extends Sink<T>>) Key.get(sink));
expose(exposedSink);
ParameterizedType lookup = Types.newParameterizedType(LookupTable.class, lit.getType());
Key<LookupTable<T>> exposedLookup = (Key<LookupTable<T>>) Key.get(lookup, annotation);
bind(exposedLookup).to((Key<? extends LookupTable<T>>) Key.get(lookup));
expose(exposedLookup);
}
});
}
/**
* Installs a counter into the module, so that the following will work for an
* annotation {@code @IOExceptions}:
*
* <pre> {@code
* @Inject
* MyConstructor(@IOExceptions Counter ioExceptionsCounter) {
* …
* }
* }</pre>
*/
@SuppressWarnings("javadoc") // See above.
protected final void installCounter(final Class<? extends Annotation> annotation, final CounterModule pipelineModule) {
checkNotNull(annotation);
checkNotNull(pipelineModule);
install(new PrivateModule() {
@Override protected void configure() {
install(pipelineModule);
bindConstant().annotatedWith(CounterName.class).to(annotation.getName());
bind(Counter.class).annotatedWith(annotation).to(Counter.class);
expose(Counter.class).annotatedWith(annotation);
}
});
}
}