package org.karmaexchange.task;
import java.io.IOException;
import java.io.PrintWriter;
import org.karmaexchange.dao.Event;
import org.karmaexchange.dao.Organization;
import org.karmaexchange.task.LeaderboardMapper.UserKarmaRecord;
import org.karmaexchange.util.OfyUtil;
import org.karmaexchange.util.ServletUtil;
import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
import com.google.appengine.api.appidentity.AppIdentityServiceFailureException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.tools.mapreduce.MapReduceJob;
import com.google.appengine.tools.mapreduce.MapReduceSettings;
import com.google.appengine.tools.mapreduce.MapReduceSpecification;
import com.google.appengine.tools.mapreduce.Marshallers;
import com.google.appengine.tools.mapreduce.inputs.DatastoreInput;
import com.google.appengine.tools.mapreduce.outputs.NoOutput;
import com.googlecode.objectify.Key;
@SuppressWarnings("serial")
public class ComputeLeaderboardServlet extends TaskQueueAdminTaskServlet {
private static final String JOB_NAME = "ComputeLeaderboards";
private static final String WORKER_QUEUE = "mapreduce-workers";
private static final int DEFAULT_MAP_SHARD_COUNT = 2;
private static final int DEFAULT_REDUCE_SHARD_COUNT = 2;
private static final String PIPELINE_STATUS_PATH = "/_ah/pipeline/status.html";
private static final String PIPELINE_STATUS_ID_PARAM = "root";
@Override
protected void execute() throws IOException {
String id = startMapReduce();
// Cron jobs display an error on the app engine console if the return status code is not
// between 200 and 299.
resp.setContentType("text/plain");
PrintWriter statusWriter =
resp.getWriter();
statusWriter.println(JOB_NAME + " map reduce initiated: " +
getMapReduceStatusUrl(id));
}
public static String startMapReduce() {
MapReduceSpecification<Entity, Key<Organization>, UserKarmaRecord, Void, Void> mapReduceSpec =
createMapReduceSpec();
MapReduceSettings settings =
getMapReduceSettings();
return MapReduceJob.start(mapReduceSpec, settings);
}
private static MapReduceSpecification<Entity, Key<Organization>, UserKarmaRecord, Void, Void>
createMapReduceSpec() {
String eventKind = OfyUtil.getKind(Event.class);
return new MapReduceSpecification.Builder<>(
new DatastoreInput(eventKind, DEFAULT_MAP_SHARD_COUNT),
new LeaderboardMapper(),
new LeaderboardReducer(),
new NoOutput<Void, Void>())
.setKeyMarshaller(Marshallers.<Key<Organization>>getSerializationMarshaller())
.setValueMarshaller(Marshallers.<UserKarmaRecord>getSerializationMarshaller())
.setJobName(JOB_NAME)
.setNumReducers(DEFAULT_REDUCE_SHARD_COUNT)
.build();
}
private static MapReduceSettings getMapReduceSettings() {
return new MapReduceSettings.Builder()
.setBucketName(getGcsBucketName())
.setWorkerQueueName(WORKER_QUEUE)
// .setModule(module) // if queue is null will use the current queue or "default" if none
.build();
}
private static String getGcsBucketName() {
try {
return AppIdentityServiceFactory.getAppIdentityService().getDefaultGcsBucketName();
} catch (AppIdentityServiceFailureException ex) {
// ignore
}
return null;
}
private String getMapReduceStatusUrl(String mapReduceJobId) {
String baseUrl = ServletUtil.getBaseUri(req);
return getMapReduceStatusUrl(baseUrl, mapReduceJobId);
}
public static String getMapReduceStatusUrl(String baseUrl, String mapReduceJobId) {
return baseUrl + PIPELINE_STATUS_PATH + "?" +
PIPELINE_STATUS_ID_PARAM + "=" + mapReduceJobId;
}
}