package org.swellrt.server.box.servlet; import com.google.inject.Inject; import com.mongodb.AggregationOutput; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.util.JSON; import org.swellrt.server.box.index.ModelIndexerModule; import org.waveprotocol.box.server.authentication.SessionManager; import org.waveprotocol.box.server.persistence.mongodb.MongoDbProvider; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.util.logging.Log; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class QueryModelService extends BaseService { private static final Log LOG = Log.get(QueryModelService.class); private DBCollection store; @Inject public QueryModelService(SessionManager sessionManager, MongoDbProvider mongoDbProvider) { super(sessionManager); try { this.store = mongoDbProvider.getDBCollection(ModelIndexerModule.MONGO_COLLECTION_MODELS); } catch (Exception e) { LOG.warning("Unable to get MongoDB collection. SwellRT servlet won't work!", e); this.store = null; } } private DBObject parseParam(String param) throws IOException { DBObject objectQuery = null; if (param == null || param.isEmpty()) { objectQuery = new BasicDBObject(); } else { objectQuery = (DBObject) JSON.parse(param); } return objectQuery; } private DBCursor getQueryResult(DBObject objectQuery, DBObject objectProjection, BasicDBList limitPartQuery) { DBCursor result; objectQuery.put("$or", limitPartQuery); // You cannot currently mix including and excluding fields if (!objectProjection.toMap().containsValue(1)) { // exclude internal mongoDb _id objectProjection.put("_id", 0); // exclude wavelet id objectProjection.put("wavelet_id", 0); } result = store.find(objectQuery, objectProjection); return result; } private Iterable<DBObject> getAggregateResult(DBObject objectAggregate, BasicDBList limitPartQuery) { Iterable<DBObject> result; DBObject objectQuery = new BasicDBObject(); objectQuery.put("$or", limitPartQuery); DBObject matchQuery = new BasicDBObject(); matchQuery.put("$match", objectQuery); ArrayList<DBObject> args = new ArrayList<DBObject>(); Iterator<String> i = objectAggregate.keySet().iterator(); while (i.hasNext()) { args.add((DBObject) objectAggregate.get(i.next())); } DBObject[] argsArray = new DBObject[args.size()]; args.toArray(argsArray); AggregationOutput r = store.aggregate(matchQuery, argsArray); result = r.results(); return result; } @Override public void execute(HttpServletRequest req, HttpServletResponse response) throws IOException { if (store == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error accesing data model index"); return; } ParticipantId participantId = null; try { participantId = getLoggedInUser(req); } catch (ServiceException e) { sendResponseError(response, e.getHttpResponseCode(), e.getServiceResponseCode()); return; } // Either... /rest/[api_version]/model?q={MongoDB query}&p={MongoDB // projection} // or... /rest/[api_version]/model?a={MongoDB aggregate query} String aggregate = req.getParameter("a"); String query = req.getParameter("q"); DBObject objectAggregate = null; DBObject objectQuery = null; DBObject objectProjection = null; BasicDBList limitPartQuery = new BasicDBList(); // Get models where user is participant limitPartQuery.add(new BasicDBObject("participants", participantId.getAddress())); // Get public models limitPartQuery.add(new BasicDBObject("participants", "@" + participantId.getDomain())); Iterable<DBObject> result; // Aggregate Case: if (aggregate != null) { objectAggregate = parseParam(aggregate); if (objectAggregate == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad aggregate JSON format"); return; } result = getAggregateResult(objectAggregate, limitPartQuery); } // Query or Query + Projection Case: else if (query != null) { objectQuery = parseParam(query); if (objectQuery == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad JSON format"); return; } String projection = req.getParameter("p"); if (projection != null) { objectProjection = parseParam(projection); if (objectProjection == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad JSON format"); return; } } else { objectProjection = new BasicDBObject(); } result = getQueryResult(objectQuery, objectProjection, limitPartQuery); } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Parameters for query or aggregation not found"); return; } StringBuilder JSONbuilder = new StringBuilder(); JSONbuilder.append("{\"result\":["); Iterator<DBObject> it = result.iterator(); while (it.hasNext()) { JSON.serialize(it.next(), JSONbuilder); if (it.hasNext()) JSONbuilder.append(","); } JSONbuilder.append("]}"); // Replace relative URLs with absolute ones // Attachments are server from context /, wave's original context UrlBuilder urlBuilder = ServiceUtils.getUrlBuilder(req, ""); ServiceUtils.completeRelativeUrls(JSONbuilder, "url", urlBuilder); ServiceUtils.completeRelativeUrls(JSONbuilder, "thumbnail", urlBuilder); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); response.setHeader("Cache-Control", "no-store"); response.getWriter().append(JSONbuilder); } }