/* Copyright (c) 2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.osm.internal.coordcache;
import java.io.File;
import java.io.FileFilter;
import java.util.List;
import java.util.Random;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.osm.internal.OSMCoordinateSequence;
import org.locationtech.geogig.osm.internal.OSMCoordinateSequenceFactory;
import org.locationtech.geogig.storage.bdbje.EnvironmentBuilder;
import com.google.common.base.Preconditions;
import com.sleepycat.bind.tuple.LongBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.CacheMode;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.vividsolutions.jts.geom.CoordinateSequence;
/**
* A {@link PointCache} that uses a temporary BDB JE database inside the repository's
* {@code .geogig/tmp}
*/
public class BDBJEPointCache implements PointCache {
private static final OSMCoordinateSequenceFactory CSFAC = OSMCoordinateSequenceFactory
.instance();
private static final Random random = new Random();
private Environment environment;
private Database database;
public BDBJEPointCache(Platform platform) {
String envName = "tmpPointCache_" + Math.abs(random.nextInt());
EnvironmentConfig envCfg;
envCfg = new EnvironmentConfig();
envCfg.setAllowCreate(true);
envCfg.setTransactional(false);
envCfg.setSharedCache(true);
envCfg.setCacheMode(CacheMode.MAKE_COLD);
envCfg.setDurability(Durability.COMMIT_NO_SYNC);
envCfg.setConfigParam(EnvironmentConfig.LOG_FILE_MAX, String.valueOf(1024 * 1024 * 1024));
envCfg.setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, "false");
envCfg.setConfigParam(EnvironmentConfig.ENV_RUN_CHECKPOINTER, "false");
envCfg.setConfigParam("je.evictor.lruOnly", "false");
envCfg.setConfigParam("je.evictor.nodesPerScan", "1000");
EnvironmentBuilder environmentBuilder = new EnvironmentBuilder(platform);
environmentBuilder.setRelativePath("tmp", "pointcache", envName);
environmentBuilder.setIsStagingDatabase(true);
environmentBuilder.setConfig(envCfg);
this.environment = environmentBuilder.get();
DatabaseConfig dbc = new DatabaseConfig();
dbc.setAllowCreate(true);
dbc.setTemporary(true);
this.database = this.environment.openDatabase(null, "pointcache", dbc);
}
@Override
public void put(Long nodeId, OSMCoordinateSequence coord) {
Preconditions.checkNotNull(nodeId, "id is null");
Preconditions.checkNotNull(coord, "coord is null");
Preconditions.checkArgument(1 == coord.size(), "coord list size is not 1");
DatabaseEntry key = new DatabaseEntry();
LongBinding.longToEntry(nodeId.longValue(), key);
int[] c = coord.ordinates();
DatabaseEntry data = CoordinateBinding.objectToEntry(c);
database.put(null, key, data);
}
@Override
public synchronized void dispose() {
if (environment == null) {
return;
}
final File envHome = environment.getHome();
try {
database.close();
} catch (RuntimeException e) {
throw new RuntimeException("Error closing point cache", e);
} finally {
database = null;
try {
environment.close();
} finally {
environment = null;
envHome.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.delete();
}
});
envHome.delete();
}
}
}
private static final class CoordinateBinding extends TupleBinding<int[]> {
private static final CoordinateBinding INSTANCE = new CoordinateBinding();
public static final DatabaseEntry objectToEntry(int[] coord) {
DatabaseEntry data = new DatabaseEntry();
INSTANCE.objectToEntry(coord, data);
return data;
}
public static int[] entryToCoord(DatabaseEntry data) {
return INSTANCE.entryToObject(data);
}
@Override
public int[] entryToObject(TupleInput input) {
return new int[] { input.readInt(), input.readInt() };
}
@Override
public void objectToEntry(int[] c, TupleOutput output) {
output.writeInt(c[0]);
output.writeInt(c[1]);
}
}
@Override
public CoordinateSequence get(List<Long> ids) {
Preconditions.checkNotNull(ids, "ids is null");
OSMCoordinateSequence cs = CSFAC.create(ids.size());
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry data = new DatabaseEntry();
for (int index = 0; index < ids.size(); index++) {
Long nodeID = ids.get(index);
LongBinding.longToEntry(nodeID.longValue(), key);
OperationStatus status = database.get(null, key, data, LockMode.DEFAULT);
if (!OperationStatus.SUCCESS.equals(status)) {
String msg = String.format("node id %s not found", nodeID);
throw new IllegalArgumentException(msg);
}
int[] c = CoordinateBinding.entryToCoord(data);
cs.setOrdinate(index, 0, c[0]);
cs.setOrdinate(index, 1, c[1]);
}
return cs;
}
}