package org.swellrt.server.box.servlet; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.swellrt.model.generic.Model; import org.swellrt.server.box.objectapi.ObjectApi; import org.swellrt.server.box.objectapi.ObjectApiException; import org.swellrt.server.box.objectapi.OpRecorderWavelet; import org.waveprotocol.box.common.comms.WaveClientRpc; import org.waveprotocol.box.server.authentication.SessionManager; import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer; import org.waveprotocol.box.server.frontend.ClientFrontend; import org.waveprotocol.box.server.frontend.ClientFrontend.OpenListener; import org.waveprotocol.box.server.frontend.CommittedWaveletSnapshot; import org.waveprotocol.box.server.waveserver.WaveletProvider; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta; import org.waveprotocol.wave.model.id.IdFilter; import org.waveprotocol.wave.model.id.IdGeneratorImpl; import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.id.WaveletName; import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; import org.waveprotocol.wave.model.operation.wave.WaveletDelta; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec; import org.waveprotocol.wave.util.logging.Log; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.inject.Inject; import com.typesafe.config.Config; /** * A REST service to edit swellrt collaborative objects. (Draft version) * * <pre> * <code> * POST /swell/object/waveid/path * { json } * </code> * </pre> * * <code>path</code> uses "/" as separator. * <br/> * TODO implement GET and DELETE operations * * @author pablojan@gmail.com (Pablo Ojanguren) * */ public class ObjectApiService extends BaseService { private static final Log LOG = Log.get(ObjectApiService.class); private final WaveletProvider waveletProvider; private final IdURIEncoderDecoder decoder = new IdURIEncoderDecoder(new JavaUrlCodec()); @Inject public ObjectApiService(SessionManager sessionManager, Config config, WaveletProvider waveletProvider) { super(sessionManager); this.waveletProvider = waveletProvider; } @Override public void execute(HttpServletRequest req, HttpServletResponse response) throws IOException { try { ParticipantId participantId = getLoggedInUser(req); String requestPath = SwellRtServlet.getCleanPathInfo(req); WaveletName waveletName = extractWaveletName(requestPath); String path = extractObjectPath(requestPath); String method = req.getMethod(); if (!method.equalsIgnoreCase("POST") || !method.equalsIgnoreCase("GET") ) { new ServiceException("Invalid HTTP method", HttpServletResponse.SC_BAD_REQUEST, RC_INVALID_HTTP_METHOD); } CommittedWaveletSnapshot snapshot = waveletProvider.getSnapshot(waveletName); OpRecorderWavelet recorderWavelet = new OpRecorderWavelet(snapshot.snapshot, snapshot.committedVersion, participantId); Model model = Model.create(recorderWavelet.getWavelet(), participantId, new IdGeneratorImpl.Seed() { @Override public String get() { return req.getSession().getId(); } }); JsonParser jsonParser = new JsonParser(); JsonElement jsonBody = null; if (method.equalsIgnoreCase("POST")) { jsonBody = jsonParser.parse(new InputStreamReader(req.getInputStream())); ObjectApi.doUpdate(model, path, jsonBody); for (WaveletDelta delta : recorderWavelet.getDeltas()) { ProtocolWaveletDelta protocolDelta = CoreWaveletOperationSerializer.serialize(delta); waveletProvider.submitRequest(waveletName, protocolDelta, new WaveletProvider.SubmitRequestListener() { @Override public void onSuccess(int operationsApplied, HashedVersion hashedVersionAfterApplication, long applicationTimestamp) { LOG.info("Object Operation ["+method+","+requestPath+"] applied "+operationsApplied+" operations, resulting version "+hashedVersionAfterApplication.getVersion()+" by "+participantId.getAddress()); } @Override public void onFailure(String errorMessage) { LOG.info("Object Operation error ["+method+","+requestPath+"] "+errorMessage); } }); } } else if (method.equalsIgnoreCase("GET")) { JsonElement json = ObjectApi.doGet(model, path); LOG.info("Object Operation ["+method+","+requestPath+"] by "+participantId.getAddress()); sendResponse(response, json); } else if (method.equalsIgnoreCase("DELETE")) { // TODO implement } } catch (ServiceException e) { sendResponseError(response, e.getHttpResponseCode(), e.getServiceResponseCode()); } catch (JsonParseException e) { sendResponseError(response, HttpServletResponse.SC_BAD_REQUEST, RC_INVALID_JSON_SYNTAX); } catch (ObjectApiException e) { sendResponseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getCode()); } catch (Exception e) { sendResponseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, RC_INTERNAL_SERVER_ERROR); } } private String extractObjectPath(String requestPath) throws ServiceException { try { String[] parts = requestPath.split("/"); String path = ""; for (int i = 4; i < parts.length; i++) { if (!path.isEmpty()) { path += "."; } path += parts[i]; } return path; } catch (Exception e) { throw new ServiceException(e.getMessage(), HttpServletResponse.SC_BAD_REQUEST, RC_INVALID_OBJECT_PATH); } } private WaveletName extractWaveletName(String requestPath) throws ServiceException { try { String[] pathParts = requestPath.split("/"); String serialWaveletName = pathParts[2]+ "/" + pathParts[3] + "/" + Model.WAVELET_SWELL_ROOT; return decoder.uriPathToWaveletName(serialWaveletName); } catch (Exception e) { throw new ServiceException(e.getMessage(), HttpServletResponse.SC_BAD_REQUEST, RC_INVALID_OBJECT_ID); } } }