package cz.cuni.mff.d3s.been.taskapi;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cz.cuni.mff.d3s.been.core.persistence.EntityCarrier;
import cz.cuni.mff.d3s.been.core.persistence.EntityID;
import cz.cuni.mff.d3s.been.mq.IMessageQueue;
import cz.cuni.mff.d3s.been.mq.IMessageSender;
import cz.cuni.mff.d3s.been.mq.MessagingException;
import cz.cuni.mff.d3s.been.persistence.*;
import cz.cuni.mff.d3s.been.results.Result;
import cz.cuni.mff.d3s.been.socketworks.NamedSockets;
import cz.cuni.mff.d3s.been.socketworks.twoway.Requestor;
import cz.cuni.mff.d3s.been.util.JSONUtils;
import cz.cuni.mff.d3s.been.util.JsonException;
final class JSONResultFacade implements ResultFacade, ResultPersisterCatalog {
private static final Logger log = LoggerFactory.getLogger(JSONResultFacade.class);
private final ObjectMapper om;
private final QuerySerializer querySerializer;
private final JSONUtils jsonUtils;
private final IMessageQueue<String> queue;
private final Collection<Persister> allocatedPersisters;
/** Whether this instance has been cleaned-up */
private boolean isPurged = false;
String taskId, contextId, benchmarkId;
private JSONResultFacade(IMessageQueue<String> queue) {
this.queue = queue;
this.om = new ObjectMapper();
this.querySerializer = new QuerySerializer();
this.allocatedPersisters = new HashSet<>();
om.setSerializationConfig(om.getSerializationConfig().without(Feature.FAIL_ON_EMPTY_BEANS).withVisibilityChecker(
new FieldVisibilityChecker()));
om.setDeserializationConfig(om.getDeserializationConfig().withVisibilityChecker(new FieldVisibilityChecker()));
om.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
jsonUtils = JSONUtils.newInstance(om);
}
/** Create a new result serialization facade */
static JSONResultFacade create(IMessageQueue<String> queue) {
return new JSONResultFacade(queue);
}
@Override
public <T extends Result> T createResult(Class<T> resultClass) throws DAOException {
try {
final T inst = resultClass.newInstance();
inst.setTaskId(taskId);
inst.setContextId(contextId);
inst.setBenchmarkId(benchmarkId);
inst.setCreated(System.currentTimeMillis());
return inst;
} catch (InstantiationException | IllegalAccessException e) {
throw new DAOException("Cannot create result", e);
}
}
@Override
public synchronized void persistResult(Result result, String group) throws DAOException {
if (result == null) {
throw new DAOException("Cannot serialize a null object.");
}
EntityCarrier ec = null;
String serializedResult = null;
try {
serializedResult = om.writeValueAsString(result);
} catch (IOException e) {
throw new DAOException(String.format("Unable to serialize Result %s to JSON.", result.toString()), e);
}
ec = new EntityCarrier();
ec.setEntityId(new EntityID().withKind("result").withGroup(group));
ec.setEntityJSON(serializedResult);
log.trace("Facade serialized a result into >>{}<<", serializedResult);
try (final IMessageSender<String> sender = queue.createSender()) {
sendRC(ec, sender);
} catch (MessagingException e) {
throw new DAOException("Cannot persist a result", e);
}
}
@Override
public synchronized
<T extends Result>
Collection<T>
query(Query fetchQuery, Class<T> resultClass) throws DAOException {
Requestor requestor = null;
String queryString = null;
String replyString = null;
QueryChecks.assertIsFetch(fetchQuery);
QueryChecks.assertIsResult(fetchQuery);
try {
queryString = querySerializer.serializeQuery(fetchQuery);
} catch (JsonException e) {
throw new DAOException("Failed to serialize query", e);
}
try {
requestor = Requestor.create(NamedSockets.TASK_RESULT_QUERY_0MQ.getConnection());
} catch (MessagingException e) {
throw new DAOException("Failed to create result query requestor", e);
}
log.trace("Querying persistence with {}", queryString);
try {
replyString = requestor.request(queryString);
} finally {
try {
requestor.close();
} catch (MessagingException e) {
log.error("Result querying connection left hanging. Task will not finish.", e);
}
}
if (replyString == null) {
throw new DAOException(String.format("Unknown failure when processing request %s", queryString));
}
log.trace("Persistence replied {}", replyString);
try {
final QueryAnswer answer = querySerializer.deserializeAnswer(replyString);
if (!answer.isCarryingData()) {
throw new DAOException(String.format(
"Query returned with no data. Answer status is: '%s'",
answer.getStatus().getDescription()));
}
return jsonUtils.deserialize(answer.getData(), resultClass);
} catch (JsonException e) {
throw new DAOException(String.format("Failed to deserialize results matching query %s", queryString), e);
}
}
// @Override
// Not permitted because of general contract, but remains here as a proof of concept
public synchronized void delete(Query deleteQuery) throws DAOException {
QueryChecks.assertIsDelete(deleteQuery);
QueryChecks.assertIsResult(deleteQuery);
String queryString = null;
Requestor requestor = null;
String answerString = null;
try {
queryString = querySerializer.serializeQuery(deleteQuery);
} catch (JsonException e) {
throw new DAOException("Failed to serialize query", e);
}
try {
requestor = Requestor.create(NamedSockets.TASK_RESULT_QUERY_0MQ.getConnection());
} catch (MessagingException e) {
throw new DAOException("Failed to create query requestor", e);
}
try {
answerString = requestor.request(queryString);
} finally {
try {
requestor.close();
} catch (MessagingException e) {
throw new DAOException("Querying connection left hanging. Task will not finish", e);
}
}
try {
final QueryAnswer answer = querySerializer.deserializeAnswer(answerString);
if (!QueryStatus.OK.equals(answer.getStatus())) {
throw new DAOException(String.format("Query failed: %s", answer.getStatus().getDescription()));
}
} catch (JsonException e) {
throw new DAOException("Failed to deserialize query answer", e);
}
}
@Override
public synchronized Persister createResultPersister(String group) throws DAOException {
final EntityID eid = new EntityID().withKind("result").withGroup(group);
return createPersister(eid);
}
synchronized Persister createPersister(EntityID entityID) throws DAOException {
if (isPurged) {
throw new DAOException("Result Facade is already purged!");
}
try {
final JSONPersister persister = new JSONPersister(entityID, queue.createSender(), this);
allocatedPersisters.add(persister);
return persister;
} catch (MessagingException e) {
// TODO rethink whether forwarding the stacktrace to the user is reasonable here
throw new DAOException("Cannot open result sending channel.", e);
}
}
@Override
public synchronized void unhook(Persister persister) {
if (allocatedPersisters.contains(persister)) {
allocatedPersisters.remove(persister);
}
}
/** Clean up any dangling persisters */
synchronized void purge() {
isPurged = true;
Collection<Persister> copy = new ArrayList<>(allocatedPersisters);
for (Persister persister : copy) {
log.warn("Persister {} was not closed, purging automatically", persister.toString());
persister.close();
}
}
private void sendRC(final EntityCarrier rc, final IMessageSender<String> sender) throws DAOException {
try {
final String serializedRC = om.writeValueAsString(rc);
log.trace("About to request this serialized result carrier to Host Runtime: >>{}<<", serializedRC);
sender.send(serializedRC);
// TODO: Temporary bug workaround for JeroMQ big message bug
// If sending big message and closing the socket immediately after JereMQ will
// sometimes corrupt the message. This needs fixing at JeroMQ side ...
if (serializedRC.length() > 100_000) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//quell
}
}
} catch (IOException e) {
throw new DAOException("Unable to serialize result carrier to JSON", e);
} catch (MessagingException e) {
throw new DAOException("Unable to request serialized result carrier to Host Runtime");
}
}
/**
* A persister implementation that serializes the object into JSON.
*
* @author darklight
*/
private class JSONPersister implements Persister {
private final EntityID entityId;
private final IMessageSender<String> sender;
private final ResultPersisterCatalog unhookCatalog;
/**
* Initialize a JSON persister bound to a specific persistence collection.
*
* @param entityId
* Persistent entity to bind to (determines target collection)
* @param sender
* Sender to use for result trafficking
* @param unhookCatalog
* Catalog to use for uregistering once this persister is closed
*/
JSONPersister(EntityID entityId, IMessageSender<String> sender, ResultPersisterCatalog unhookCatalog) {
this.entityId = entityId;
this.sender = sender;
this.unhookCatalog = unhookCatalog;
}
@Override
public void persist(Result result) throws DAOException {
String serializedResult = null;
try {
serializedResult = om.writeValueAsString(result);
} catch (IOException e) {
throw new DAOException(String.format("Unable to serialize Result %s to json", result.toString()), e);
}
log.trace("Persister serialized a result into >>{}<<", serializedResult);
final EntityCarrier rc = new EntityCarrier();
rc.setEntityId(entityId);
rc.setEntityJSON(serializedResult);
sendRC(rc, sender);
}
@Override
public void close() {
unhookCatalog.unhook(this);
sender.close();
}
}
}