package org.karmaexchange.util.derived;
import static java.lang.String.format;
import static org.karmaexchange.util.OfyService.ofy;
import java.io.IOException;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.bind.annotation.XmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.karmaexchange.dao.Event;
import org.karmaexchange.dao.KeyWrapper;
import org.karmaexchange.dao.User;
import org.karmaexchange.dao.Event.ParticipantType;
import org.karmaexchange.dao.derived.EventSourceInfo;
import org.karmaexchange.resources.derived.SourceEvent;
import org.karmaexchange.resources.derived.SourceUser;
import org.karmaexchange.resources.msg.ErrorResponseMsg;
import org.karmaexchange.resources.msg.ErrorResponseMsg.ErrorInfo;
import com.googlecode.objectify.Key;
public class SourceEventSyncUtil {
private enum RegistrationAction {
REGISTER,
UNREGISTER
}
public static void upsertParticipant(Key<Event> eventKey, Key<User> userKey,
ParticipantType participantType) {
// TODO(avaliani): handle other states
if (participantType != ParticipantType.REGISTERED) {
return;
}
syncRegistration(eventKey, userKey, RegistrationAction.REGISTER);
}
public static void deleteParticipant(Key<Event> eventKey, Key<User> userKey) {
syncRegistration(eventKey, userKey, RegistrationAction.UNREGISTER);
}
private static void syncRegistration(Key<Event> eventKey, Key<User> userKey,
RegistrationAction action) {
Event event = ofy().load().key(eventKey).now();
if (event == null) {
throw ErrorResponseMsg.createException("event not found",
ErrorInfo.Type.BAD_REQUEST);
}
User user = ofy().load().key(userKey).now();
if (user == null) {
throw ErrorResponseMsg.createException("user not found",
ErrorInfo.Type.BAD_REQUEST);
}
if (event.getSourceEventInfo() == null) {
return;
}
EventSourceInfo sourceInfo = ofy().load().key(
EventSourceInfo.createKey(
KeyWrapper.toKey(event.getOrganization()))).now();
if (sourceInfo == null) {
throw ErrorResponseMsg.createException(
"derived event generator info missing", ErrorInfo.Type.BACKEND_SERVICE_FAILURE);
}
URL url;
try {
url = new URL(sourceInfo.getRegistrationUrl());
} catch (MalformedURLException e) {
throw ErrorResponseMsg.createException(e, ErrorInfo.Type.BACKEND_SERVICE_FAILURE);
}
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Content-type", "application/json");
connection.setReadTimeout(0);
ObjectMapper mapper = new ObjectMapper();
RegistrationRequest registrationReq = new RegistrationRequest(
action,
event.getSourceEventInfo().getId());
SourceUser sourceUser =
new SourceUser(user, sourceInfo);
UpdateRequest updateReq = new UpdateRequest(
sourceInfo.getSecret(),
registrationReq,
sourceUser);
mapper.writeValue(connection.getOutputStream(), updateReq);
int responseCode = connection.getResponseCode();
StringWriter responseContent = new StringWriter();
IOUtils.copy(
connection.getInputStream(), responseContent);
if (responseCode == HttpURLConnection.HTTP_OK) {
RegistrationResponse registrationResp =
mapper.readValue(responseContent.toString(), RegistrationResponse.class);
// TODO(avaliani): leverage the sourceEvent to do a full event update (vs. explicitly
// processing the update). The update should happen even on error. We can be out of
// sync we need to update our db.
if (registrationResp.error != null) {
registrationResp.error.convertAndThrowError();
}
} else {
throw ErrorResponseMsg.createException(
format("Error: remote db request failed [%s, %d, %s,\n response='%s'\n]",
url,
connection.getResponseCode(),
connection.getResponseMessage(),
responseContent),
ErrorInfo.Type.BACKEND_SERVICE_FAILURE);
}
} catch (IOException e) {
throw ErrorResponseMsg.createException(e, ErrorInfo.Type.PARTNER_SERVICE_FAILURE);
}
}
@XmlRootElement
@Data
@NoArgsConstructor
@AllArgsConstructor
private static class UpdateRequest {
private String secretKey;
private RegistrationRequest registrationReq;
private SourceUser sourceUser;
}
@Data
@NoArgsConstructor
private static class RegistrationRequest {
private RegistrationAction action;
private String eventId;
// TODO(avaliani): update num volunteers.
private int numVolunteers;
public RegistrationRequest(RegistrationAction action, String eventId) {
this.action = action;
this.eventId = eventId;
// TODO(avaliani): this should be user initialized.
this.numVolunteers = 1;
}
}
@XmlRootElement
@Data
private static class RegistrationResponse {
SourceEvent sourceEvent;
SourceErrorInfo error;
}
@Data
private static class SourceErrorInfo {
public enum ErrorType {
AUTHENTICATION_FAILURE,
INVALID_PARAM,
REGISTRATION_LIMIT_REACHED,
OBJECT_NOT_FOUND,
DML_EXCEPTION,
OTHER_EXCEPTION
}
private ErrorType type;
private String message;
private String stackTrace;
public void convertAndThrowError() {
if (type == ErrorType.REGISTRATION_LIMIT_REACHED) {
throw ErrorResponseMsg.createException(
"the event has reached the max registration limit",
ErrorInfo.Type.LIMIT_REACHED);
} else if (type == ErrorType.OBJECT_NOT_FOUND) {
throw ErrorResponseMsg.createException("event not found",
ErrorInfo.Type.BAD_REQUEST);
} else {
throw ErrorResponseMsg.createException(
format("Error: exception updating remote db [%s,%s]", type, message),
ErrorInfo.Type.BACKEND_SERVICE_FAILURE);
}
}
}
}