package eu.ttbox.androgister.web.sync; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MappingJsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.base.Function; public abstract class SyncRequestReader<ENTITY extends EntitySyncable> { private static final Logger LOG = LoggerFactory.getLogger(SyncRequestReader.class); // Request private static final String TAG_HEADER = "header"; private static final String TAG_SYNCED = "synced"; private static final String TAG_UPDATED = "updated"; private static final String TAG_DELETED = "deleted"; // Response private static final String TAG_UPDATE_ACK = "updateAck"; // Instance private final Class<ENTITY> entityClass; public SyncRequestReader(Class<ENTITY> entityClass) { super(); this.entityClass = entityClass; } public void syncFile(InputStream is, Writer out) throws IOException { // Init Jackson final ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); MappingJsonFactory jsonFactory = new MappingJsonFactory(mapper); // Init Writer final JsonGenerator jgen = jsonFactory.createGenerator(out); jgen.writeStartObject(); // Read The file JsonParser jp = jsonFactory.createJsonParser(is); // advance stream to START_ARRAY first: JsonToken firstToken = jp.nextToken(); // if (firstToken != JsonToken.START_ARRAY) { // throw new RuntimeException("Invalid Format"); // } SyncHeader entityHeader = null; final HashMap<UUID, Long> entityIgnore = new HashMap<UUID, Long>(); JsonToken currentToken = null; // Type Ref JavaType typeEntityRef = TypeFactory.defaultInstance().constructType(new TypeReference<SyncEntity<UUID>>() { }); CollectionType typeRef = TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, typeEntityRef); // Read while ((currentToken = jp.nextToken()) != JsonToken.END_OBJECT) { String fieldname = jp.getCurrentName(); if (TAG_HEADER.equals(fieldname)) { currentToken = jp.nextToken(); // move to value entityHeader = mapper.readValue(jp, SyncHeader.class); LOG.info("Header : {}", entityHeader); } else if (TAG_SYNCED.equals(fieldname)) { currentToken = jp.nextToken(); // move to value List<SyncEntity<UUID>> resut = mapper.readValue(jp, typeRef); for (SyncEntity<UUID> entity : resut) { entityIgnore.put(entity.serverId, entity.versionDate); LOG.info("synced : {}", entity); } } else if (TAG_UPDATED.equals(fieldname)) { syncFileUpdated(mapper, jp, jgen, entityIgnore); } else if (TAG_DELETED.equals(fieldname)) { currentToken = jp.nextToken(); // move to value List<SyncEntity<UUID>> resut = mapper.readValue(jp, typeRef); for (SyncEntity<UUID> entity : resut) { LOG.info("deleted : {}", entity); } } } jp.close(); // Get other update form Db syncDatabaseUpdated(mapper, jgen, entityIgnore, entityHeader); // Close Json Writer jgen.writeEndObject(); jgen.close(); } private void syncFileUpdated(final ObjectMapper mapper, JsonParser jp, final JsonGenerator jgen, final HashMap<UUID, Long> entityIgnore) throws JsonParseException, IOException { JsonToken firstToken = jp.nextToken(); if (firstToken != JsonToken.START_ARRAY) { throw new RuntimeException("Invalid Format : Token [" + firstToken + "]"); } // Prepare Generator jgen.writeArrayFieldStart(TAG_UPDATE_ACK); while (jp.nextToken() != JsonToken.END_ARRAY) { // Read Entity ENTITY entity = mapper.readValue(jp, entityClass); LOG.info("updated Entity : {}", entity); // TODO Save Server entity.setServerId(UUID.randomUUID()); entity.setVersionDate(Long.valueOf(System.currentTimeMillis())); // Keep Update data entityIgnore.put(entity.getServerId(), entity.getVersionDate()); // Write the status mapper.writeValue(jgen, entity); jgen.flush(); } // Close field generator jgen.writeEndArray(); } protected Function<ENTITY, Boolean> createCallbackWriter(final ObjectMapper mapper, final JsonGenerator jgen, final HashMap<UUID, Long> entityIgnore) { // Read Other modify Function<ENTITY, Boolean> callback = new Function<ENTITY, Boolean>() { @Override public Boolean apply(ENTITY entity) { try { Long clientVersion = entityIgnore.get(entity.getServerId()); if (clientVersion == null || !clientVersion.equals(entity.getVersionDate())) { // Send server version mapper.writeValue(jgen, entity); } } catch (Exception e) { LOG.error("Callback read error : " + e.getMessage(), e); return Boolean.FALSE; } return Boolean.TRUE; } }; return callback; } private void syncDatabaseUpdated(final ObjectMapper mapper, final JsonGenerator jgen, final HashMap<UUID, Long> entityIgnore, SyncHeader entityHeader) throws JsonGenerationException, IOException { Function<ENTITY, Boolean> callback = createCallbackWriter(mapper, jgen, entityIgnore); // Prepare Generator jgen.writeArrayFieldStart(TAG_UPDATED); // Read All Datas mockSync(jgen, callback); // Close field generator jgen.writeEndArray(); } public abstract void mockSync(final JsonGenerator jgen, Function<ENTITY, Boolean> callback); }