/*
* Copyright 2007-2008 Amazon Technologies, 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://aws.amazon.com/apache2.0
*
* This file 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 com.amazonaws.mturk.service.axis;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.apache.log4j.Logger;
import org.xml.sax.InputSource;
import com.amazon.mechanicalturk.common.domain.XslTransformer;
import com.amazonaws.mturk.addon.BatchItemCallback;
import com.amazonaws.mturk.addon.HITDataInput;
import com.amazonaws.mturk.addon.HITDataOutput;
import com.amazonaws.mturk.addon.HITDataReader;
import com.amazonaws.mturk.addon.HITDataWriter;
import com.amazonaws.mturk.addon.HITProperties;
import com.amazonaws.mturk.addon.HITQuestion;
import com.amazonaws.mturk.addon.HITResults;
import com.amazonaws.mturk.addon.HITTypeResults;
import com.amazonaws.mturk.addon.MTurkConstants;
import com.amazonaws.mturk.addon.QAPValidator;
import com.amazonaws.mturk.dataschema.ObjectFactory;
import com.amazonaws.mturk.dataschema.QuestionFormAnswers;
import com.amazonaws.mturk.dataschema.QuestionFormAnswersType;
import com.amazonaws.mturk.requester.Assignment;
import com.amazonaws.mturk.requester.AssignmentStatus;
import com.amazonaws.mturk.requester.EventType;
import com.amazonaws.mturk.requester.GetAccountBalanceResult;
import com.amazonaws.mturk.requester.GetAssignmentsForHITResult;
import com.amazonaws.mturk.requester.GetAssignmentsForHITSortProperty;
import com.amazonaws.mturk.requester.GetQualificationRequestsResult;
import com.amazonaws.mturk.requester.GetQualificationRequestsSortProperty;
import com.amazonaws.mturk.requester.GetQualificationsForQualificationTypeResult;
import com.amazonaws.mturk.requester.GetReviewableHITsResult;
import com.amazonaws.mturk.requester.GetReviewableHITsSortProperty;
import com.amazonaws.mturk.requester.HIT;
import com.amazonaws.mturk.requester.NotificationSpecification;
import com.amazonaws.mturk.requester.NotificationTransport;
import com.amazonaws.mturk.requester.Qualification;
import com.amazonaws.mturk.requester.QualificationRequest;
import com.amazonaws.mturk.requester.QualificationStatus;
import com.amazonaws.mturk.requester.QualificationType;
import com.amazonaws.mturk.requester.QualificationTypeStatus;
import com.amazonaws.mturk.requester.ReviewableHITStatus;
import com.amazonaws.mturk.requester.SearchHITsResult;
import com.amazonaws.mturk.requester.SearchHITsSortProperty;
import com.amazonaws.mturk.requester.SearchQualificationTypesResult;
import com.amazonaws.mturk.requester.SearchQualificationTypesSortProperty;
import com.amazonaws.mturk.requester.SortDirection;
import com.amazonaws.mturk.service.exception.ServiceException;
import com.amazonaws.mturk.service.exception.ValidationException;
import com.amazonaws.mturk.util.ClientConfig;
import com.amazonaws.mturk.util.FileUtil;
import com.amazonaws.mturk.util.VelocityUtil;
/**
* The RequesterService class provides a set of simplified APIs and convenience methods.
* It extends the RequesterServiceRaw class.
*/
public class RequesterService extends RequesterServiceRaw {
//-------------------------------------------------------------
// Constants
//-------------------------------------------------------------
public static final int DEFAULT_PAGE_NUM = 1;
public static final int DEFAULT_PAGE_SIZE = 10;
public static final SortDirection DEFAULT_SORT_DIRECTION = SortDirection.Ascending;
public static final int LOAD_ALL = -1;
private static final int MAX_BATCH = 500; // maximum batch size for batch chunk
public static final long DEFAULT_ASSIGNMENT_DURATION_IN_SECONDS = (long) 60 * 60; // 1 hour
public static final long DEFAULT_AUTO_APPROVAL_DELAY_IN_SECONDS = (long) 60 * 60 * 24 * 15; // 15 days
public static final long DEFAULT_LIFETIME_IN_SECONDS = (long) 60 * 60 * 24 * 3; // 3 days
// QualificationTypeIds for System Qualifications
public static final String ABANDONMENT_RATE_QUALIFICATION_TYPE_ID = "00000000000000000070";
public static final String APPROVAL_RATE_QUALIFICATION_TYPE_ID = "000000000000000000L0";
public static final String REJECTION_RATE_QUALIFICATION_TYPE_ID = "000000000000000000S0";
public static final String RETURN_RATE_QUALIFICATION_TYPE_ID = "000000000000000000E0";
public static final String SUBMISSION_RATE_QUALIFICATION_TYPE_ID = "00000000000000000000";
public static final String LOCALE_QUALIFICATION_TYPE_ID = "00000000000000000071";
private static final String DATASCHEMA_PACKAGE_PREFIX = "com.amazonaws.mturk.dataschema";
private static final AssignmentStatus[] DEFAULT_ASSIGNMENT_STATUS = new AssignmentStatus[] {
AssignmentStatus.Approved,
AssignmentStatus.Rejected,
AssignmentStatus.Submitted
};
private static final AssignmentStatus[] SUBMITTED_ASSIGNMENT_STATUS = new AssignmentStatus[] {
AssignmentStatus.Submitted
};
private static final String[] DEFAULT_ASSIGNMENT_RESPONSE_GROUP = new String [] {
"Minimal",
"AssignmentFeedback"
};
private static final String[] DEFAULT_HIT_RESPONSE_GROUP = new String [] {
"Minimal",
"HITDetail",
"HITQuestion",
"HITAssignmentSummary"
};
//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------
private static Logger log = Logger.getLogger(RequesterService.class);
//-------------------------------------------------------------
// Constructors
//-------------------------------------------------------------
@Deprecated
public RequesterService() {
super();
}
public RequesterService(ClientConfig config) {
super(config);
}
//-------------------------------------------------------------
// Methods - Public
//-------------------------------------------------------------
/**
* Creates a HIT using default values for the HIT properties not given as parameters.
* The request uses the default responseGroup of "Minimal".
*
* @param title
* @param description
* @param reward
* @param question
* @param maxAssignments
* @return The created HIT
* @throws ServiceException
*/
public HIT createHIT(String title, String description, double reward, String question,
int maxAssignments) throws ServiceException {
return this.createHIT(
title,
description, // description
reward,
question,
maxAssignments,
false
);
}
/**
* Creates a HIT using defaults for the HIT properties not given as parameters.
* The request uses either the default or full responseGroup.
*
* @param title
* @param description
* @param reward
* @param question
* @param maxAssignments
* @param getFullResponse
* @return The created HIT
* @throws ServiceException
*/
public HIT createHIT(String title, String description, double reward, String question,
int maxAssignments, boolean getFullResponse) throws ServiceException {
// Include HIT detail, HIT Question, and Assignment summary in response
String[] responseGroup = null;
if (getFullResponse == true) {
responseGroup = new String [] { "Minimal", "HITDetail",
"HITQuestion", "HITAssignmentSummary" };
}
return super.createHIT(
null, // HITTypeId
title,
description, // description
null, // keywords
question,
reward,
DEFAULT_ASSIGNMENT_DURATION_IN_SECONDS,
DEFAULT_AUTO_APPROVAL_DELAY_IN_SECONDS,
DEFAULT_LIFETIME_IN_SECONDS,
maxAssignments,
null, //requesterAnnotation
null, // qualificationRequirements
responseGroup
);
}
/**
* Updates a HIT using defaults for the HIT properties not given as parameters.
*
* @param hitId
* @param title - if null, the HIT's current title is used
* @param description - if null, the HIT's current description is used
* @param reward - if null, the HIT's current reward is used
* @return The new HITType Id
* @throws ServiceException
*/
public String updateHIT(String hitId, String title, String description, String keywords,
Double reward) throws ServiceException {
if (title == null || description == null || keywords == null || reward == null) {
HIT currentHIT = this.getHIT(hitId);
if (title == null) {
title = currentHIT.getTitle();
}
if (description == null) {
description = currentHIT.getDescription();
}
if (keywords == null) {
keywords = currentHIT.getKeywords();
}
if (reward == null) {
reward = currentHIT.getReward().getAmount().doubleValue();
}
}
String newHITTypeId = this.registerHITType(
DEFAULT_AUTO_APPROVAL_DELAY_IN_SECONDS,
DEFAULT_ASSIGNMENT_DURATION_IN_SECONDS,
reward,
title,
keywords,
description,
null); // qualificationRequirements
this.changeHITTypeOfHIT(hitId, newHITTypeId);
return newHITTypeId;
}
/**
* Updates HITs in bulk.
*
* @param input
* @param newHITTypeId
* @return Array of hitIds that were successfully updated
* @throws ServiceException
*/
public String[] updateHITs(String[] hitIds, String newHITTypeId) throws ServiceException {
List<String> successes = new ArrayList<String>(hitIds.length);
// split work
log.debug(String.format("Updating %d HITs with max memory %d", hitIds.length, Runtime.getRuntime().maxMemory()));
AsyncReply[] replies = new AsyncReply[MAX_BATCH];
int numBatches = hitIds.length / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > hitIds.length) {
iEnd = hitIds.length;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
// submit to work queue
for (int i=iStart; i<iEnd; i++) {
replies[i-iStart] = super.changeHITTypeOfHITAsync(hitIds[i], newHITTypeId, null);
}
// wait for results
for (int i=iStart; i<iEnd; i++) {
try {
replies[i-iStart].getResult();
successes.add(hitIds[i]);
} catch (ServiceException e) {
// don't add it to the success list
log.error("Error updating HIT " + hitIds[i] + " to HIT type " + newHITTypeId + ": " + e.getLocalizedMessage());
}
}
}
return successes.toArray(new String[successes.size()]);
}
public void deleteHITs(String[] hitIds, boolean approve, boolean expire, BatchItemCallback callback) {
if (hitIds != null && hitIds.length > 0) {
DeleteHITCommand[] commands = new DeleteHITCommand[MAX_BATCH];
log.debug(String.format("Deleting %d HITs with max memory %d", hitIds.length, Runtime.getRuntime().maxMemory()));
int successCount=0;
int failureCount=0;
int numBatches = hitIds.length / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > hitIds.length) {
iEnd = hitIds.length;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
for (int i=iStart; i<iEnd; i++) {
commands[i-iStart] = new DeleteHITCommand(i+1, hitIds[i], approve, expire, this, callback);
commands[i-iStart].execute();
}
// calculate results
for (int i=iStart; i<iEnd; i++) {
if (commands[i-iStart].hasSucceeded()) {
successCount++;
}
else {
failureCount++;
}
}
}
if (callback == null) {
log.info("Deleted "+successCount+" HITs. " + failureCount + " HITs failed to delete.");
}
}
}
public void extendHITs(String[] hitIds, Integer maxAssignmentsIncrement, Long expirationIncrementInSeconds,
BatchItemCallback callback)
throws ServiceException {
if (hitIds == null || hitIds.length == 0) {
return;
}
if (maxAssignmentsIncrement == null && expirationIncrementInSeconds==null) {
throw new ServiceException("Neither maxAssignmentsIncrement nor expirationIncrementInSeconds are specified");
}
log.debug(String.format("Extending %d HITs with max memory %d", hitIds.length, Runtime.getRuntime().maxMemory()));
AsyncReply[] replies = new AsyncReply[MAX_BATCH];
int numBatches = hitIds.length / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > hitIds.length) {
iEnd = hitIds.length;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
// submit requests to work queue
for (int i=iStart; i<iEnd; i++) {
replies[i-iStart] = super.extendHITAsync(hitIds[i], maxAssignmentsIncrement, expirationIncrementInSeconds, null);
}
// wait for results
for (int i=iStart; i<iEnd; i++) {
try {
Object result = replies[i-iStart].getResult();
if (callback != null) {
callback.processItemResult(hitIds[i], true, result, null);
}
else {
log.info(String.format("[%s] Successfully extended HIT (%d/%d)",
hitIds[i], i, hitIds.length));
}
} catch (ServiceException e) {
if (callback != null) {
callback.processItemResult(hitIds[i], false, null, e);
}
else {
log.error(String.format("[%s] Failed to extend HIT (%d/%d): %s",
hitIds[i], i, hitIds.length, e.getLocalizedMessage()));
}
}
}
}
}
/***
* Approves all assignments using the Axis worker thread pool.
*
* @param assignmentIds Array of assignments to approve
* @param requesterFeedback Feedback (comments) for the assignments
* @param defaultFeedback Default feedback used when no requesterFeedback is specified for an assignment ID
* @param callback Callback function for item results processing
* @throws ServiceException
*/
public void approveAssignments(String[] assignmentIds, String[] requesterFeedback, String defaultFeedback,
BatchItemCallback callback)
throws ServiceException {
if (assignmentIds == null || assignmentIds.length == 0) {
return;
}
if (requesterFeedback != null && requesterFeedback.length != assignmentIds.length) {
throw new ServiceException("Number of assignments to approve must match number of approval comments (requester feedback)");
}
// preprocess feedback comments
if (requesterFeedback == null) {
requesterFeedback = new String[assignmentIds.length];
}
if (defaultFeedback != null) {
for (int i=0; i<assignmentIds.length; i++) {
if (requesterFeedback[i]==null) {
requesterFeedback[i] = defaultFeedback;
}
}
}
log.debug(String.format("Approving %d assignments with max memory %d", assignmentIds.length, Runtime.getRuntime().maxMemory()));
AsyncReply[] replies = new AsyncReply[MAX_BATCH];
int numBatches = assignmentIds.length / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > assignmentIds.length) {
iEnd = assignmentIds.length;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
// submit requests to work queue
for (int i=iStart; i<iEnd; i++) {
replies[i-iStart] = super.approveAssignmentAsync(assignmentIds[i], requesterFeedback[i], null);
}
// wait for results
for (int i=iStart; i<iEnd; i++) {
try {
Object result = replies[i-iStart].getResult();
if (callback != null) {
callback.processItemResult(assignmentIds[i], true, result, null);
}
else {
log.info("[" + assignmentIds[i] + "] Assignment successfully approved " +
(requesterFeedback[i] != null && requesterFeedback[i].length() > 0 ? " with comment (" + requesterFeedback[i] + ")" : ""));
}
} catch (ServiceException e) {
if (callback != null) {
callback.processItemResult(assignmentIds[i], false, null, e);
}
else {
log.error("Error approving assignment " + assignmentIds[i] +
(requesterFeedback[i] != null && requesterFeedback[i].length() > 0 ? " with comment (" + requesterFeedback[i] + ")" : "") +
": " + e.getLocalizedMessage());
}
}
}
}
}
/**
* Retrieves a HIT by HIT Id. The request uses full responseGroup.
*
* @param hitId
* @return A HIT object
* @throws ServiceException
*/
public HIT getHIT(String hitId) throws ServiceException {
// Include HIT detail, HIT Question, and Assignment summary in response
return super.getHIT(hitId, DEFAULT_HIT_RESPONSE_GROUP);
}
/**
* Creates a Qualification Type using default values for the Qualification Type properties
* not given as parameters.
*
* @param name
* @param keywords
* @param description
* @return The created QualificationType
* @throws ServiceException
*/
public QualificationType createQualificationType(String name, String keywords, String description) throws ServiceException {
return super.createQualificationType(name, keywords, description,
QualificationTypeStatus.Active,
(long) 0, // retryDelayInSeconds
null,
null,
null, // testDurationInSeconds
null, // autoGranted
null // autoGrantedValue
);
}
/**
* Creates a Qualification Type with no test using default values for the Qualification Type
* properties not given as parameters.
*
* @param qualificationTypeId
* @param description
* @param status
* @return The updated QualificationType
* @throws ServiceException
*/
public QualificationType updateQualificationType(String qualificationTypeId, String description,
QualificationTypeStatus status) throws ServiceException {
return super.updateQualificationType(qualificationTypeId, description, status,
null, // test
null, // answerKey
(Long) null, // testDurationInSeconds
(Long) null, // retryDelayInSeconds
(Boolean) null, // autoGranted
(Integer) null // autoGrantedValue
);
}
/**
* Retrieves workers' Qualifications found on the requested page for the given
* Qualification Type. The request uses the default responseGroup of "Minimal".
*
* @param qualificationTypeId
* @param pageNum
* @return An array of Qualifications
* @throws ServiceException
* @deprecated
*/
public Qualification[] getQualicationsForQualificationType(String qualificationTypeId, int pageNum) throws ServiceException {
GetQualificationsForQualificationTypeResult result =
super.getQualificationsForQualificationType(qualificationTypeId,
QualificationStatus.Granted,
pageNum,
DEFAULT_PAGE_SIZE);
return result.getQualification();
}
/**
* Retrieves workers' Qualifications found on the requested page for the given
* Qualification Type. The request uses the default responseGroup of "Minimal".
*
* @param qualificationTypeId
* @param pageNum
* @return An array of Qualifications
* @throws ServiceException
*/
public Qualification[] getQualificationsForQualificationType(String qualificationTypeId, int pageNum) throws ServiceException {
GetQualificationsForQualificationTypeResult result =
super.getQualificationsForQualificationType(qualificationTypeId,
QualificationStatus.Granted,
pageNum,
DEFAULT_PAGE_SIZE);
return result.getQualification();
}
public Qualification[] getAllQualificationsForQualificationType(String qualificationTypeId) throws Exception {
List<Qualification> results = new ArrayList<Qualification>();
int pageNum = 1;
do {
Qualification[] qualifications =
this.getQualificationsForQualificationType(qualificationTypeId, pageNum);
if (qualifications != null) {
// Add the results
Collections.addAll(results, qualifications);
}
if (qualifications == null || qualifications.length < DEFAULT_PAGE_SIZE) {
// Check if we're on the last page or not
break;
}
} while (true);
return (Qualification[]) results.toArray(new Qualification[results.size()]);
}
/**
* Retrieves workers' QualificationRequests found on the first page for the given
* Qualification Type. The results are sorted by SubmitTime.
*
* @param qualificationTypeId
* @return An array of QualificationRequests
* @throws ServiceException
*/
public QualificationRequest[] getQualificationRequests(String qualificationTypeId)
throws ServiceException {
GetQualificationRequestsResult result = super.getQualificationRequests(qualificationTypeId,
DEFAULT_SORT_DIRECTION,
GetQualificationRequestsSortProperty.SubmitTime,
DEFAULT_PAGE_NUM,
DEFAULT_PAGE_SIZE);
return result.getQualificationRequest();
}
/**
* Retrieves all QualificationRequests for the given
* Qualification Type. The results are sorted by SubmitTime.
*
* @param qualificationTypeId
* @return An array of QualificationRequests
* @throws ServiceException
*/
public QualificationRequest[] getAllQualificationRequests(String qualificationTypeId) throws ServiceException {
List<QualificationRequest> results = new ArrayList<QualificationRequest>();
int pageNum = 1;
QualificationRequest[] thisPage = null;
do {
GetQualificationRequestsResult result = super.getQualificationRequests(qualificationTypeId,
DEFAULT_SORT_DIRECTION,
GetQualificationRequestsSortProperty.SubmitTime,
pageNum,
DEFAULT_PAGE_SIZE);
if(result == null) {
break;
}
thisPage = result.getQualificationRequest();
if(thisPage != null && thisPage.length > 0) {
Collections.addAll(results,thisPage);
}
pageNum++;
} while (thisPage != null && thisPage.length >= DEFAULT_PAGE_SIZE);
return (QualificationRequest[]) results.toArray(new QualificationRequest[results.size()]);
}
/**
* Retrieves workers' Assignments found on the requested page for the given HIT.
* The request uses the default responseGroup of "Minimal".
*
* @param hitId
* @param pageNum
* @return An array of Assignments
* @throws ServiceException
*/
public Assignment[] getAssignmentsForHIT(String hitId, int pageNum) throws ServiceException {
return this.getAssignmentsForHIT(hitId, pageNum, false);
}
/**
* Retrieves workers' Assignments found on the first page for the given HIT.
* The results are sorted by SubmitTime. The request uses either the default
* or full responseGroup.
*
* @param hitId
* @param pageNum
* @param getFullResponse
* @return An array of Assignments
* @throws ServiceException
*/
public Assignment[] getAssignmentsForHIT(String hitId, int pageNum, boolean getFullResponse)
throws ServiceException {
// Include AssignmentFeedback in response
String[] responseGroup = null;
if (getFullResponse == true) {
responseGroup = DEFAULT_ASSIGNMENT_RESPONSE_GROUP;
}
GetAssignmentsForHITResult result = super.getAssignmentsForHIT(hitId,
DEFAULT_SORT_DIRECTION,
DEFAULT_ASSIGNMENT_STATUS,
GetAssignmentsForHITSortProperty.SubmitTime,
pageNum, DEFAULT_PAGE_SIZE, responseGroup
);
return result.getAssignment();
}
/**
* Retrieves requester's available balance.
*
* @return requester's available balance
* @throws ServiceException
*/
public double getAccountBalance() throws ServiceException {
String defaultUnused = null;
GetAccountBalanceResult result = super.getAccountBalance(defaultUnused);
return result.getAvailableBalance().getAmount().doubleValue();
}
/**
* Retrieves requester's reviewable HITs found on the requested page for the given HIT Type.
*
* @param hitId
* @param pageNum
* @return An array of Reviewable HITs
* @throws ServiceException
*/
public HIT[] getReviewableHITs(String hitTypeId, int pageNum) throws ServiceException {
ReviewableHITStatus defaultStatus = ReviewableHITStatus.Reviewable;
GetReviewableHITsResult result = super.getReviewableHITs(
hitTypeId,
defaultStatus,
DEFAULT_SORT_DIRECTION,
GetReviewableHITsSortProperty.CreationTime,
pageNum,
DEFAULT_PAGE_SIZE
);
return result.getHIT();
}
/**
* Retrieves any HITs found on the requested page.
* The request uses the default responseGroup of "Minimal".
*
* @param pageNum
* @return An array of HITs
* @throws ServiceException
*/
public HIT[] searchHITs(int pageNum) throws ServiceException {
return this.searchHITs(pageNum, false);
}
/**
* Retrieves requester's reviewable HITs found on the requested page for the given HIT Type.
* The request uses either the default or full responseGroup.
*
* @param pageNum
* @param getFullResponse
* @return An array of HITs
* @throws ServiceException
*/
public HIT[] searchHITs(int pageNum, boolean getFullResponse) throws ServiceException {
// Include HIT detail, HIT Question, and Assignment summary in response
String[] responseGroup = null;
if (getFullResponse == true) {
responseGroup = new String [] { "Minimal", "HITDetail",
"HITQuestion", "HITAssignmentSummary" };
}
SearchHITsResult result = super.searchHITs(
DEFAULT_SORT_DIRECTION, SearchHITsSortProperty.Expiration,
pageNum, DEFAULT_PAGE_SIZE, responseGroup
);
return result.getHIT();
}
/**
* Retrieves any Qualification Types found on the requested page.
*
* @param pageNum
* @return An array of QualificationTypes
* @throws ServiceException
*/
public QualificationType[] searchQualificationTypes(int pageNum) throws ServiceException {
SearchQualificationTypesResult result = super.searchQualificationTypes(
null, // Query
false, // mustBeRequestable
true, // mustBeOwnedByCaller
DEFAULT_SORT_DIRECTION,
SearchQualificationTypesSortProperty.Name,
pageNum,
DEFAULT_PAGE_SIZE);
return result.getQualificationType();
}
//-------------------------------------------------------------
// Implementation - Convenience API
//-------------------------------------------------------------
/**
* Sets the status of the given HIT as Reviewable.
*
* @param hitId
* @throws ServiceException
*/
public void setHITAsReviewable(String hitId) throws ServiceException {
super.setHITAsReviewing(hitId,
true // revert
);
}
/**
* Sets the status of the given HIT as Reviewing.
*
* @param hitId
* @throws ServiceException
*/
public void setHITAsReviewing(String hitId) throws ServiceException {
super.setHITAsReviewing(hitId,
false // revert
);
}
/**
* Retrieves all active HITs in the system.
* The request uses the full responseGroup.
*
* @return An array of HITs
* @throws ServiceException
*/
public HIT[] searchAllHITs() throws ServiceException {
List<HIT> results = new ArrayList<HIT>();
int numHITsInAccount = this.getTotalNumHITsInAccount();
double numHITsInAccountDouble = new Double(numHITsInAccount);
double pageSizeDouble = new Double(DEFAULT_PAGE_SIZE);
double numPagesDouble = Math.ceil(numHITsInAccountDouble / pageSizeDouble);
int numPages = (new Double(numPagesDouble)).intValue();
for (int i = 1; i <= numPages; i = i + 1)
{
HIT[] hits = this.searchHITs(i, true);
Collections.addAll(results, hits);
}
return (HIT[]) results.toArray(new HIT[results.size()]);
}
/**
* Retrieves all active Qualifications in the system.
*
* @return An array of QualificationTypes
* @throws ServiceException
*/
public QualificationType[] getAllQualificationTypes() throws ServiceException {
List<QualificationType> results = new ArrayList<QualificationType>();
int pageNum = 1;
do {
QualificationType[] qt = this.searchQualificationTypes(pageNum);
if (qt != null) {
// Add the results
Collections.addAll(results, qt);
}
// Check if we're on the last page or not
if (qt == null || qt.length < DEFAULT_PAGE_SIZE)
break;
pageNum++;
} while (true);
return (QualificationType[]) results.toArray(new QualificationType[results.size()]);
}
/**
* Retrieves all of requester's reviewable HITs of the given HIT Type.
*
* @param hitTypeId
* @return An array of Reviewable HITs
* @throws ServiceException
*/
public HIT[] getAllReviewableHITs(String hitTypeId) throws ServiceException {
List<HIT> results = new ArrayList<HIT>();
int pageNum = 1;
do {
HIT[] hit = this.getReviewableHITs(hitTypeId, pageNum);
if (hit != null) {
// Add the results
Collections.addAll(results, hit);
}
// Check if we're on the last page or not
if (hit == null || hit.length < DEFAULT_PAGE_SIZE)
break;
pageNum++;
} while (true);
return (HIT[]) results.toArray(new HIT[results.size()]);
}
/**
* Retrieves all of requester's assignments for the given HIT.
*
* @param hitId
* @return An array of Assignments
* @throws ServiceException
*/
public Assignment[] getAllAssignmentsForHIT(String hitId) throws ServiceException {
return getAllAssignmentsForHIT(hitId, DEFAULT_ASSIGNMENT_STATUS);
}
/**
* Retrieves all of requester's assignments for the given HIT for which rewiewable work is submitted.
*
* @param hitId
* @return An array of Assignments
* @throws ServiceException
*/
public Assignment[] getAllSubmittedAssignmentsForHIT(String hitId) throws ServiceException {
return getAllAssignmentsForHIT(hitId, SUBMITTED_ASSIGNMENT_STATUS);
}
/**
* Retrieves all of requester's assignments for the given HIT that are in a certain status
*
* @param hitId
* @return An array of Assignments
* @throws ServiceException
*/
public Assignment[] getAllAssignmentsForHIT(String hitId, AssignmentStatus[] status) throws ServiceException {
List<Assignment> results = new ArrayList<Assignment>();
int pageNum = 1;
do {
GetAssignmentsForHITResult result = super.getAssignmentsForHIT(hitId,
DEFAULT_SORT_DIRECTION,
status,
GetAssignmentsForHITSortProperty.SubmitTime,
pageNum, DEFAULT_PAGE_SIZE, DEFAULT_ASSIGNMENT_RESPONSE_GROUP
);
Assignment[] assignment = result.getAssignment();
if (assignment != null) {
// Add the results
Collections.addAll(results, assignment);
}
// Check if we're on the last page or not
if (assignment == null || assignment.length < DEFAULT_PAGE_SIZE)
break;
pageNum++;
} while (true);
return (Assignment[]) results.toArray(new Assignment[results.size()]);
}
/**
* Creates a single checkbox Qualification Type. The QualificationTest simply asks the worker
* to check off the box to receive a Qualification immediately.
*
* @param name
* @param description
* @param keywords
* @return The created QualificationType
* @throws ServiceException
*/
public QualificationType createSingleCheckboxQualificationType(String name, String description,
String keywords) throws ServiceException {
return super.createQualificationType(name, keywords, description,
QualificationTypeStatus.Active,
(long) 0, // retryDelayInSeconds
getBasicCheckboxQualificationTest(name),
getBasicCheckboxQualificationAnswerKey(),
(long) 60 * 60, // testDurationInSeconds
null,
null // autoGrantedValue
);
}
/**
* Disposes the given Qualification Type. The Qualification Type becomes inactive.
*
* @param qualificationTypeId
* @return The modified QualificationType
* @throws ServiceException
*/
public QualificationType disposeQualificationType(String qualificationTypeId) {
return this.updateQualificationType(qualificationTypeId,
null, // don't change description
QualificationTypeStatus.Inactive
);
}
/**
* Retrieves the total number of active HITs for the requester.
*
* @return The total number of active HITs for the requester
* @throws ServiceException
*/
public int getTotalNumHITsInAccount() throws ServiceException {
SearchHITsSortProperty defaultSortProperty =
SearchHITsSortProperty.Expiration;
SearchHITsResult result = super.searchHITs(
DEFAULT_SORT_DIRECTION,
defaultSortProperty,
1, // pageNum
DEFAULT_PAGE_SIZE, null // responseGroup
);
return (result==null) ? 0 : result.getTotalNumResults();
}
/**
* Sets up an email notification setting for the given HIT Type.
*
* @see http://docs.amazonwebservices.com/AWSMechanicalTurkRequester/2006-10-31/ApiReference_NotificationDataStructureArticle.html
* @param hitTypeId
* @param emailAddress
* @param event
* @throws ServiceException
*/
public void sendTestEmailEventNotification(String hitTypeId, String emailAddress, EventType event) {
NotificationSpecification ns = new NotificationSpecification();
ns.setDestination(emailAddress);
ns.setTransport(NotificationTransport.Email);
ns.setVersion(RequesterServiceRaw.NOTIFICATION_VERSION);
ns.setEventType(new EventType[] { event });
super.setHITTypeNotification(hitTypeId, ns, true);
}
/**
* Extracts the QuestionFormAnswers object from the given answer XML.
*
* @see http://docs.amazonwebservices.com/AWSMechanicalTurkRequester/2006-10-31/ApiReference_QuestionFormAnswersDataStructureArticle.html
* @param answerXML
* @return A QuestionFormAnswers object that contains the answers
* @throws ServiceException
*/
public static QuestionFormAnswers parseAnswers(String answerXML) {
try {
JAXBContext jc = JAXBContext.newInstance(RequesterService.DATASCHEMA_PACKAGE_PREFIX,
ObjectFactory.class.getClassLoader());
Unmarshaller u = jc.createUnmarshaller();
QuestionFormAnswers qfa = (QuestionFormAnswers)
u.unmarshal(new InputSource(new StringReader(answerXML)));
return qfa;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getAnswerValue(String assignmentId, QuestionFormAnswersType.AnswerType answer) {
return getAnswerValue(assignmentId, answer, false);
}
/**
* Extracts the answer values from the given AnswerType object. When the answer type is
* Selections, returns the selections separated by the pipe character. When the answer
* type is UploadedFileKey, returns the S3 file key followed by the file's size in bytes.
*
* @param assignmentId If null, the upload URL might be invalid
* @param answer
* @param includeQuestionId Prepend the answer with the associated QuestionIdentifier and a tab
* @return A String representation of the answer
* @throws ServiceException
*/
public static String getAnswerValue(String assignmentId, QuestionFormAnswersType.AnswerType answer, boolean includeQuestionId) {
String result = includeQuestionId ? result = answer.getQuestionIdentifier() + "\t" : "";
String val = "";
if (answer.getFreeText() != null) {
val = answer.getFreeText();
}
else if (answer.getOtherSelectionText() != null) {
val = answer.getOtherSelectionText();
}
else if (answer.getSelectionIdentifier() != null
&& answer.getUploadedFileKey() == null) {
Iterator iter = answer.getSelectionIdentifier().iterator();
while (iter.hasNext()) {
val += iter.next() + HITResults.MULTI_ANSWER_DELIMITER;
}
if (val.length() > 0) {
val = val.substring(0, val.length() - 1);
}
}
else {
try {
String url = "http://requester.mturk.com/mturk/downloadAnswer?assignmentId="
+ assignmentId + "&questionId=" + URLEncoder.encode(answer.getQuestionIdentifier(), "UTF-8");
result += url;
} catch (UnsupportedEncodingException e) {
result += answer.getUploadedFileKey() + HITResults.MULTI_ANSWER_DELIMITER + answer.getUploadedFileSizeInBytes();
}
}
if (val.length()==0) {
result += HITResults.EMPTY_ANSWER; // Feature 1816806 (missing columns when value is NULL)
}
else {
result += val;
}
return result;
}
@Deprecated
public HIT[] createHITs(HITDataReader input, HITProperties props, HITQuestion question) {
return createHITs( input, props, question, RequesterService.LOAD_ALL );
}
/**
* Creates HITs in bulk.
*
* @param input
* @param properties
* @param question
* @param success
* @param failure
* @return An array of HIT objects
* @throws ServiceException
*/
public HIT[] createHITs(HITDataInput input, HITProperties props, HITQuestion question,
HITDataOutput success, HITDataOutput failure) throws Exception {
return createHITs(input, props, question, RequesterService.LOAD_ALL, success, failure);
}
@Deprecated
public HIT[] createHITs(HITDataReader input, HITProperties props, HITQuestion question, int numHITToLoad) {
String prefix = input.getFileName();
if ( prefix == null || prefix.length() == 0 ) {
prefix = "input";
}
HITDataOutput success = null;
HITDataOutput failure = null;
try {
success = new HITDataWriter(prefix + ".success");
failure = new HITDataWriter(prefix + ".failure");
return createHITs(input,props,question,numHITToLoad,success,failure);
}
catch (Exception e) {
log.error("Error loading HITs", e);
}
finally {
if (success != null) {
success.close();
}
if (failure != null) {
failure.close();
}
}
return null;
}
/**
* Creates HITs in bulk.
*
* @param input
* @param properties
* @param question
* @param numHITsToLoad
* @param success
* @param failure
* @return An array of HIT objects
* @throws ServiceException
*/
public HIT[] createHITs(HITDataInput input, HITProperties props, HITQuestion question, int numHITToLoad,
HITDataOutput success, HITDataOutput failure) throws Exception {
// Create HITs
List<HIT> hits = new ArrayList<HIT>();
String[] fieldHeaders = new String[] {
HITProperties.HITField.HitId.getFieldName(),
HITProperties.HITField.HitTypeId.getFieldName()
};
boolean hasFailures = false;
if( success != null ) {
success.setFieldNames( fieldHeaders );
}
int numRecords;
if (numHITToLoad != RequesterService.LOAD_ALL) {
numRecords = Math.min(numHITToLoad, input.getNumRows()-1);
}
else {
numRecords = input.getNumRows() - 1;
}
// submit hits to work pool
AsyncReply[] replies = new AsyncReply[MAX_BATCH];
// Map of HIT types created
String hitTypeForBatch = null;
HIT hit = null;
// split work
log.debug(String.format("Creating %d HITs with max memory %d", numRecords, Runtime.getRuntime().maxMemory()));
int numBatches = numRecords / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > numRecords) {
iEnd = numRecords;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
for (int i = iStart; i < iEnd; i++) {
// Merge the input with the question
// Start from the second line since the first line contains the field names
Map<String, String> inputMap = input.getRowAsMap(i + 1);
// Merge the input with the properties
props.setInputMap(inputMap);
// we need to make sure to not create multiple hittypes for matching HITs
// due to multithreaded calls being processed at the same time
if (hitTypeForBatch == null) {
hitTypeForBatch = super.registerHITType(
props.getAutoApprovalDelay(),
props.getAssignmentDuration(),
props.getRewardAmount(),
props.getTitle(),
props.getKeywords(),
props.getDescription(),
props.getQualificationRequirements());
}
replies[i-iStart] = super.createHITAsync(
hitTypeForBatch,
null, // title
null, // description
null, // keywords
question.getQuestion(inputMap),
null, // reward
null, // assignmentDurationInSeconds
null, // autoApprovalDelayInSeconds
props.getLifetime(),
props.getMaxAssignments(),
props.getAnnotation(),
null, // qualification requirements
null, // response group
null); // async callback
}
// wait for thread pool to finish processing these requests and evaluate results
for (int i = iStart; i < iEnd; i++) {
try {
hit = ((HIT[])replies[i-iStart].getResult())[0];
hits.add(hit);
log.info("Created HIT " + (i + 1) + ": HITId=" + hit.getHITId());
if( success != null ) {
// Print to the success file
HashMap<String,String> good = new HashMap<String,String>();
good.put( fieldHeaders[0], hit.getHITId() );
good.put( fieldHeaders[1], hit.getHITTypeId() );
success.writeValues(good);
}
}
catch (Exception e) {
// Validate the question
Map<String,String> row = input.getRowAsMap(i+1);
try {
Map<String, String> inputMap = input.getRowAsMap(i + 1);
QAPValidator.validate(question.getQuestion(inputMap));
// If it passed validation, then log the exception e
log.error("[ERROR] Error creating HIT " + (i+1)
+ " (" + input.getRowValues(i+1)[0] + "): " + e.getLocalizedMessage());
}
catch (ValidationException ve) {
// Otherwise, log the validation exception in place of the service exception
log.error("[ERROR] Error creating HIT " + (i+1)
+ " (" + input.getRowValues(i+1)[0] + "): " + ve.getLocalizedMessage());
}
if( failure != null ) {
// Create the failure file
if (!hasFailures) {
hasFailures = true;
failure.setFieldNames( input.getFieldNames() );
}
// Print to the failure file
failure.writeValues(row);
}
}
}
}
if (hit != null && log.isInfoEnabled()) {
// Print the URL at which the new HIT can be viewed at the end as well
// so the user doesn't have to "scroll" up in case lots of HITs have been loaded
log.info(System.getProperty("line.separator") + "You may see your HIT(s) with HITTypeId '" + hit.getHITTypeId() + "' here: ");
log.info(System.getProperty("line.separator") + " " + getWebsiteURL()
+ "/mturk/preview?groupId=" + hit.getHITTypeId() + System.getProperty("line.separator"));
}
return (HIT[])hits.toArray(new HIT[hits.size()]);
}
/**
* Get the results for a HIT Type.
*
* @param successget
* @return A HITTypeResults object
* @throws ServiceException
*/
public HITTypeResults getHITTypeResults(HITDataInput success) {
HITTypeResults r = null;
try {
r = this.getHITTypeResults(success, null);
} catch (IOException e) {
// There shouldn't be any IO exception here
log.error("IOException thrown. Did the HIT results get printed somehow?");
}
return r;
}
public void getResults(HITDataInput success, BatchItemCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback may not be null");
}
int numRows = success.getNumRows();
String[] rowValues;
HIT hit=null;
String hitId;
Assignment[] assignments = null;
AsyncReply[] hitReplies = new AsyncReply[MAX_BATCH];
AssignmentsLoader[] assignmentLoaders = new AssignmentsLoader[MAX_BATCH];
// split work
log.debug(String.format("Retrieving results for %d HITs with max memory %d",
numRows - 1, // take off the header row
Runtime.getRuntime().maxMemory()));
int numBatches = numRows / MAX_BATCH;
for (int curBatch=0; curBatch<=numBatches; curBatch++) {
int iStart = curBatch * MAX_BATCH;
int iEnd = iStart + MAX_BATCH;
if (iEnd > numRows) {
iEnd = numRows;
}
log.debug(String.format("Processing batch %d (%d to %d)", curBatch, iStart, iEnd));
// load hits and (first) results in worker queue
for (int i=iStart; i<iEnd; i++) {
rowValues = success.getRowValues(i);
hitId = rowValues[MTurkConstants.HIT_ID_FIELD_IND];
// Skip header lines
if ( hitId.equalsIgnoreCase(MTurkConstants.HIT_ID_HEADER) )
continue;
hitReplies[i-iStart] = super.getHITAsync(hitId, DEFAULT_HIT_RESPONSE_GROUP, null);
AssignmentsLoader loader = new AssignmentsLoader(this,
hitId,
SortDirection.Ascending,
DEFAULT_ASSIGNMENT_STATUS,
GetAssignmentsForHITSortProperty.SubmitTime,
DEFAULT_ASSIGNMENT_RESPONSE_GROUP,
DEFAULT_PAGE_SIZE);
assignmentLoaders[i-iStart] = loader;
// start loading the assignments for the HIT
loader.start();
}
// process the results
for (int i=iStart; i<iEnd; i++) {
rowValues = success.getRowValues(i);
hitId = rowValues[MTurkConstants.HIT_ID_FIELD_IND];
// Skip header lines
if ( hitId.equalsIgnoreCase(MTurkConstants.HIT_ID_HEADER) ) {
continue;
}
try {
hit = ((HIT[])hitReplies[i-iStart].getResult())[0];
AssignmentsLoader loader = (AssignmentsLoader)assignmentLoaders[i-iStart];
assignments = loader.getResults();
callback.processItemResult(hitId, true, new HITResults(hit, assignments, this.config), null);
}
catch (Exception e) {
callback.processItemResult(hitId, false, null, e);
}
}
}
}
/**
* Get the results for a HIT Type and print each HIT results to a file.
*
* @param success
* @param outputFile
* @return A HITTypeResults object
* @throws IOException
* @throws ServiceException
* @deprecated
*/
public HITTypeResults getHITTypeResults(HITDataInput success, HITDataOutput output) throws IOException {
int numRows = success.getNumRows();
String[] rowValues;
String hitId;
ArrayList<HITResults> hitResultsArray = new ArrayList<HITResults>(numRows);
Assignment[] assignments = null;
HITResults r = null;
HITTypeResults hitTypeResults = new HITTypeResults();
if (output != null) {
// Print headers
log.debug("Print each HIT results as it's retrieved");
hitTypeResults.setHITDataOutput(output);
hitTypeResults.writeResultsHeader();
} else {
log.debug("Retrieve all HIT results and return them as HITTypeResults");
}
// load hits and (first) results in worker queue
AsyncReply[] hitReplies = new AsyncReply[numRows];
AsyncReply[] assignmentReplies = new AsyncReply[numRows];
for (int i=0; i<numRows; i++) {
rowValues = success.getRowValues(i);
hitId = rowValues[MTurkConstants.HIT_ID_FIELD_IND];
// Skip header lines
if ( hitId.equalsIgnoreCase(MTurkConstants.HIT_ID_HEADER) )
continue;
hitReplies[i] = super.getHITAsync(hitId, DEFAULT_HIT_RESPONSE_GROUP, null);
assignmentReplies[i] = super.getAssignmentsForHITAsync(hitId,
SortDirection.Ascending,
DEFAULT_ASSIGNMENT_STATUS,
GetAssignmentsForHITSortProperty.SubmitTime,
1,
DEFAULT_PAGE_SIZE,
DEFAULT_ASSIGNMENT_RESPONSE_GROUP,
null);
}
// process the results
for (int i=0; i<numRows; i++) {
rowValues = success.getRowValues(i);
hitId = rowValues[MTurkConstants.HIT_ID_FIELD_IND];
// Skip header lines
if ( hitId.equalsIgnoreCase(MTurkConstants.HIT_ID_HEADER) ) {
continue;
}
try {
HIT hit = ((HIT[])hitReplies[i].getResult())[0];
assignments = new Assignment[] {};
GetAssignmentsForHITResult result = ((GetAssignmentsForHITResult[])assignmentReplies[i].getResult())[0];
if (result.getAssignment() != null) {
assignments = result.getAssignment();
// if there are more pages load them in as well, otherwise use results from first page
if (assignments.length == DEFAULT_PAGE_SIZE && result.getTotalNumResults() > DEFAULT_PAGE_SIZE) {
assignments = super.getAssignmentsForHITAsync(hitId,
SortDirection.Ascending,
DEFAULT_ASSIGNMENT_STATUS,
GetAssignmentsForHITSortProperty.SubmitTime,
DEFAULT_PAGE_SIZE,
DEFAULT_ASSIGNMENT_RESPONSE_GROUP,
result,
null);
}
}
r = new HITResults(hit, assignments, this.config);
if (output != null) {
r.writeResults(output);
}
else {
hitResultsArray.add(r);
}
log.info(String.format("Retrieved HIT %d/%d, %s", i, numRows-1, hit.getHITId()));
}
catch (Exception e) {
log.error(String.format("Error retrieving HIT results for HIT %d/%d (%s): %s", i, numRows-1, hitId, e.getMessage()));
}
}
if (output != null) {
return null;
} else {
return new HITTypeResults(
hitResultsArray.toArray(
new HITResults[hitResultsArray.size()] ) );
}
}
public void previewHIT(String previewFileName, HITDataInput input, HITProperties props,
HITQuestion question) throws ServiceException {
try {
String previewString = previewHIT(input, props, question);
if (previewString != null) {
FileUtil fts = new FileUtil(previewFileName);
fts.saveString(previewString, false); // overwrite
}
}
catch (Exception e)
{
throw new ServiceException("Error generating preview file " + previewFileName, e);
}
}
/**
* Return a preview of the HIT in HTML
*
* @param input
* @param properties
* @param question
* @return An HTML preview of the HIT
* @throws ServiceException
*/
public String previewHIT(HITDataInput input, HITProperties props,
HITQuestion question) throws Exception {
if (props == null || question == null)
throw new IllegalArgumentException();
String questionXML = null;
if (input != null) {
Map<String, String> inputMap = input.getRowAsMap(1);
questionXML = question.getQuestion(inputMap);
} else {
questionXML = question.getQuestion();
}
// Validate question before preview
QAPValidator.validate(questionXML);
String questionPreview = XslTransformer.convertQAPtoHTML(questionXML);
InputStream headerURL = this.getClass().getResourceAsStream("previewHITHeader.xml");
InputStream footerURL = this.getClass().getResourceAsStream("previewHITFooter.xml");
if (headerURL == null) {
log.error("Error reading the preview header file.");
}
if (footerURL == null) {
log.error("Error reading the preview footer file.");
}
BufferedReader headerReader = new BufferedReader(new InputStreamReader(headerURL));
BufferedReader footerReader = new BufferedReader(new InputStreamReader(footerURL));
String thisLine = null;
String header = "";
String footer = "";
while ((thisLine = headerReader.readLine()) != null) { header += thisLine + System.getProperty("line.separator"); }
while ((thisLine = footerReader.readLine()) != null) { footer += thisLine + System.getProperty("line.separator"); }
headerReader.close();
footerReader.close();
NumberFormat rewardFormatter = NumberFormat.getInstance();
rewardFormatter.setMaximumFractionDigits(2);
rewardFormatter.setMinimumFractionDigits(2);
Map<String, String> headerMap = new HashMap<String, String>();
headerMap.put("requester", "[Your Requester Name Here]");
headerMap.put("title", props.getTitle());
headerMap.put("description", props.getDescription());
headerMap.put("keywords", props.getKeywords());
headerMap.put("reward", rewardFormatter.format(props.getRewardAmount()));
String mergedHeader = VelocityUtil.doMerge(header, headerMap);
String previewString = mergedHeader + questionPreview + footer;
return previewString;
}
public void appendApplicationSignature(String signature) {
super.appendApplicationSignature(signature, this.getPort());
}
//-------------------------------------------------------------
// Methods - Public Static
//-------------------------------------------------------------
/**
* Returns the URL for the Mechanical Turk website.
*
* @return URL
*/
public String getWebsiteURL() {
return this.config.getWorkerWebsiteURL();
}
/**
* Formats the given double value into the currency format.
*
* @param value
* @return formatted value
*/
public static String formatCurrency(double value) {
DecimalFormat form = new DecimalFormat("0.00"); // print up to two decimal points
return form.format(value);
}
/**
* Constructs a Question XML String that contains a single question.
*
* @param question The question phrase to ask
* @return A Question XML
*/
public static String getBasicFreeTextQuestion(String question) {
String q = "";
q += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
q += "<QuestionForm xmlns=\"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd\">";
q += " <Question>";
q += " <QuestionIdentifier>1</QuestionIdentifier>";
q += " <QuestionContent>";
q += " <Text>" + question + "</Text>";
q += " </QuestionContent>";
q += " <AnswerSpecification>";
q += " <FreeTextAnswer/>";
q += " </AnswerSpecification>";
q += " </Question>";
q += "</QuestionForm>";
return q;
}
//-------------------------------------------------------------
// Methods - Protected
//-------------------------------------------------------------
/**
* Constructs a QualificationTest XML String that contains a simple checkbox.
*
* @param name The name of the Qualification Test
* @return A QualificationTest XML
*/
protected String getBasicCheckboxQualificationTest(String name) {
String test = "";
test += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
test += "<QuestionForm xmlns=\"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd\">";
test += "<Overview><Title>" + name + "</Title></Overview>";
test += "<Question>";
test += " <QuestionIdentifier>ConfirmRequest</QuestionIdentifier>";
test += " <DisplayName>Confirm your request</DisplayName>";
test += " <IsRequired>true</IsRequired>";
test += " <QuestionContent><Text></Text></QuestionContent>";
test += " <AnswerSpecification>";
test += " <SelectionAnswer>";
test += " <StyleSuggestion>checkbox</StyleSuggestion>";
test += " <Selections>";
test += " <Selection>";
test += " <SelectionIdentifier>yes</SelectionIdentifier>";
test += " <Text>Please check the box to the left and click SUBMIT to have the qualification granted to you. If you do not want the qualification, please click CANCEL.</Text>";
test += " </Selection>";
test += " </Selections>";
test += " </SelectionAnswer>";
test += " </AnswerSpecification>";
test += "</Question>";
test += "</QuestionForm>";
return test;
}
/**
* Constructs a Qualification AnswerKey XML String for the simple checkbox QualificationTest.
* The AnswerKey would assign a Qualification Score of 100 to the worker.
*
* @return A Qualification AnswerKey XML
*/
protected String getBasicCheckboxQualificationAnswerKey() {
String answerKey = "";
answerKey += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
answerKey += "<AnswerKey xmlns=\"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/AnswerKey.xsd\">";
answerKey += "<Question>";
answerKey += " <QuestionIdentifier>ConfirmRequest</QuestionIdentifier>";
answerKey += " <AnswerOption>";
answerKey += " <SelectionIdentifier>yes</SelectionIdentifier>";
answerKey += " <AnswerScore>100</AnswerScore>";
answerKey += " </AnswerOption>";
answerKey += "</Question>";
answerKey += "</AnswerKey>";
return answerKey;
}
}