package org.icij.extract.report;
import org.icij.extract.document.Document;
import org.icij.extract.document.DocumentFactory;
import org.icij.extract.redis.*;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import org.icij.task.Options;
import org.icij.task.annotation.Option;
import org.icij.task.annotation.OptionsClass;
import org.redisson.RedissonMap;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;
import org.redisson.client.RedisOutOfMemoryException;
import org.redisson.command.CommandSyncService;
import org.redisson.connection.ConnectionManager;
/**
* A {@link ReportMap} using Redis as a backend.
*
* @author Matthew Caruana Galizia <mcaruana@icij.org>
* @since 1.0.0-beta
*/
@Option(name = "reportName", description = "The name of the report, the default of which is type-dependent.",
parameter = "name")
@Option(name = "charset", description = "Set the output encoding for text and document attributes. Defaults to UTF-8.",
parameter = "name")
@OptionsClass(ConnectionManager.class)
public class RedisReportMap extends RedissonMap<Document, Report> implements ReportMap {
/**
* The default name for a report in Redis.
*/
private static final String DEFAULT_NAME = "extract:report";
private final ConnectionManager connectionManager;
/**
* Create a Redis-backed report using the provided configuration.
*
* @param options options for connecting to Redis
*/
RedisReportMap(final DocumentFactory factory, final Options<String> options) {
this(factory, new ConnectionManagerFactory().withOptions(options).create(),
options.get("reportName").value().orElse(DEFAULT_NAME),
options.get("charset").parse().asCharset().orElse(StandardCharsets.UTF_8));
}
/**
* Instantiate a new Redis-backed report using the provided connection manager and name.
*
* @param connectionManager instantiated using {@link ConnectionManagerFactory}
* @param name the name of the report
*/
private RedisReportMap(final DocumentFactory factory, final ConnectionManager connectionManager, final String name,
final Charset charset) {
super(new ReportCodec(factory, charset), new CommandSyncService(connectionManager), null == name ?
DEFAULT_NAME : name);
this.connectionManager = connectionManager;
}
/**
* Specifies that {@link RedisOutOfMemoryException} exceptions should trigger journaling of arguments when caught.
*
* @return a collection with only one object, the {@link RedisOutOfMemoryException} class
*/
public Collection<Class<? extends Exception>> journalableExceptions() {
return Collections.singletonList(RedisOutOfMemoryException.class);
}
@Override
public void close() throws IOException {
connectionManager.shutdown();
}
/**
* Codec for a map of string keys to integer values.
*
* @author Matthew Caruana Galizia <mcaruana@icij.org>
* @since 1.0.0-beta
*/
static class ReportCodec implements Codec {
private final Decoder<Object> documentDecoder;
private final Encoder documentEncoder;
private final Decoder<Object> resultDecoder = new ResultDecoder();
private final Encoder resultEncoder = new ResultEncoder();
ReportCodec(final DocumentFactory factory, final Charset charset) {
this.documentDecoder = new DocumentDecoder(factory, charset);
this.documentEncoder = new DocumentEncoder(charset);
}
@Override
public Decoder<Object> getMapKeyDecoder() {
return documentDecoder;
}
@Override
public Decoder<Object> getMapValueDecoder() {
return resultDecoder;
}
@Override
public Encoder getMapValueEncoder() {
return resultEncoder;
}
@Override
public Encoder getMapKeyEncoder() {
return documentEncoder;
}
@Override
public Decoder<Object> getValueDecoder() {
return resultDecoder;
}
@Override
public Encoder getValueEncoder() {
return resultEncoder;
}
}
}