package org.swellrt.server.box.index; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import org.swellrt.model.ReadableBoolean; import org.swellrt.model.ReadableFile; import org.swellrt.model.ReadableList; import org.swellrt.model.ReadableMap; import org.swellrt.model.ReadableModel; import org.swellrt.model.ReadableNumber; import org.swellrt.model.ReadableString; import org.swellrt.model.ReadableText; import org.swellrt.model.ReadableType; import org.swellrt.model.ReadableTypeVisitor; import org.swellrt.model.shared.ModelUtils; import org.waveprotocol.box.attachment.AttachmentMetadata; import org.waveprotocol.box.server.attachment.AttachmentService; import org.waveprotocol.wave.media.model.AttachmentId; import org.waveprotocol.wave.model.id.InvalidIdException; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.model.wave.ParticipantId; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Stack; /** * Build a MongoDB document with the snapshot of a collaborative data model. * * @author pablojan@gmail.com (Pablo Ojanguren) * */ public class ModelIndexerVisitor implements ReadableTypeVisitor { private final BasicDBObject document; private final Stack<Object> objects; protected final Stack<String> path; protected final Map<String, String> blipIdToPathMap; private final AttachmentService attachmentService; /** * Generate a BSON view of the Wave-based Data Model. Also return a map * between collaborative data model objects' paths to the corresponding * blip/document storing it. * * @param model the Wave-based collaborative data model * @return */ public static Pair<BasicDBObject, Map<String, String>> run(ReadableModel model, AttachmentService attachmentService) { ModelIndexerVisitor visitor = new ModelIndexerVisitor(attachmentService); visitor.visit(model); return Pair.<BasicDBObject, Map<String, String>> of(visitor.getDBObject(), visitor.getblipIdToPathMap()); } protected ModelIndexerVisitor(AttachmentService attachmentService) { this.document = new BasicDBObject(); this.objects = new Stack<Object>(); this.path = new Stack<String>(); this.blipIdToPathMap = new HashMap<String, String>(); this.attachmentService = attachmentService; } protected BasicDBObject getDBObject() { return document; } protected Map<String, String> getblipIdToPathMap() { return blipIdToPathMap; } @Override public void visit(ReadableModel instance) { document.append("wave_id", ModelUtils.serialize(instance.getWaveId())); document.append("wavelet_id", ModelUtils.serialize(instance.getWaveletId())); // Add participants BasicDBList participants = new BasicDBList(); for (ParticipantId p : instance.getParticipants()) { participants.add(p.getAddress()); } document.append("participants", participants); // Root map path.push("root"); instance.getRoot().accept(this); document.put("root", objects.pop()); path.pop(); } @Override public void visit(ReadableString instance) { this.objects.add(instance.getValue()); } @Override public void visit(ReadableMap instance) { BasicDBObject mapDBObject = new BasicDBObject(); for (String k : instance.keySet()) { path.push(k); // Avoid issues on non initialized blips (blips with no content) ReadableType t = instance.get(k); if (t != null) { instance.get(k).accept(this); mapDBObject.put(k, objects.pop()); } path.pop(); } objects.push(mapDBObject); } @Override public void visit(ReadableList<? extends ReadableType> instance) { // TODO(pablojan) add getDocumentedId to ReadableList BasicDBList listDBObject = new BasicDBList(); int i = 0; for (ReadableType t : instance.getValues()) { path.push("" + (i++)); t.accept(this); listDBObject.add(objects.pop()); path.pop(); } objects.push(listDBObject); } @Override public void visit(ReadableText instance) { blipIdToPathMap.put(instance.getDocumentId(), getStringPath()); // TODO (pablojan) serialize annotations BasicDBObject textDBObject = new BasicDBObject(); // textDBObject.append("annotations", ""); // TODO (pablojan) add test case for Text objects textDBObject.append("excerpt", instance.getText(0, 256)); textDBObject.append("author", instance.getAuthor().getAddress()); textDBObject.append("contributors", toDBList(instance.getContributors())); textDBObject.append("lastmodtime", instance.getLastUpdateTime()); objects.push(textDBObject); } @Override public void visit(ReadableFile instance) { AttachmentMetadata metadata = null; try { if (attachmentService != null) if (instance.getValue().getId() != null && !instance.getValue().getId().isEmpty()) metadata = attachmentService.getMetadata(AttachmentId.deserialise(instance.getValue().getId())); } catch (IOException e) { // TODO handle exception } catch (InvalidIdException e) { // TODO handle exception } BasicDBObject fileDBObject = new BasicDBObject(); if (metadata != null) { fileDBObject.append("id", metadata.getAttachmentId()); fileDBObject.append("filename", metadata.getFileName()); fileDBObject.append("url", metadata.getAttachmentUrl()); fileDBObject.append("mimetype", metadata.getMimeType()); fileDBObject.append("size", metadata.getSize()); fileDBObject.append("thumbnail", metadata.getThumbnailUrl()); } else { fileDBObject.append("id", instance.getValue().serialise()); } objects.add(fileDBObject); } @Override public void visit(ReadableNumber instance) { Double doubleValue = instance.getValueDouble(); if (doubleValue != null) { this.objects.add(doubleValue.doubleValue()); } else { Integer intValue = instance.getValueInt(); if (intValue != null) { this.objects.add(intValue.intValue()); } else this.objects.add(null); } } @Override public void visit(ReadableBoolean instance) { this.objects.add(instance.getValue()); } protected String getStringPath() { String strPath = ""; for (String e : path) { if (!strPath.isEmpty()) strPath += "."; strPath += e; } return strPath; } protected BasicDBList toDBList(Set<ParticipantId> participantSet) { BasicDBList listDBObject = new BasicDBList(); for (ParticipantId p : participantSet) listDBObject.add(p.getAddress()); return listDBObject; } }