/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.box.server.robots.dataapi;
import com.google.common.collect.Lists;
import com.google.wave.api.InvalidRequestException;
import com.google.wave.api.JsonRpcResponse;
import com.google.wave.api.OperationRequest;
import com.google.wave.api.ProtocolVersion;
import com.google.wave.api.RobotSerializer;
import com.google.wave.api.data.converter.EventDataConverterManager;
import com.google.wave.api.impl.GsonFactory;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import net.oauth.OAuthValidator;
import org.waveprotocol.box.server.robots.OperationContext;
import org.waveprotocol.box.server.robots.OperationContextImpl;
import org.waveprotocol.box.server.robots.OperationResults;
import org.waveprotocol.box.server.robots.OperationServiceRegistry;
import org.waveprotocol.box.server.robots.util.ConversationUtil;
import org.waveprotocol.box.server.robots.util.LoggingRequestListener;
import org.waveprotocol.box.server.robots.util.OperationUtil;
import org.waveprotocol.box.server.waveserver.WaveletProvider;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.util.logging.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* The base {@link HttpServlet} for {@link DataApiServlet} and
* {@link ActiveApiServlet}.
*
* @author ljvderijk@google.com (Lennard de Rijk)
* @author vega113@gmail.com (Yuri Z.)
*/
@SuppressWarnings("serial")
public abstract class BaseApiServlet extends HttpServlet {
private static final Log LOG = Log.get(BaseApiServlet.class);
private static final WaveletProvider.SubmitRequestListener LOGGING_REQUEST_LISTENER =
new LoggingRequestListener(LOG);
private static final String JSON_CONTENT_TYPE = "application/json";
private final RobotSerializer robotSerializer;
private final EventDataConverterManager converterManager;
private final WaveletProvider waveletProvider;
private final OperationServiceRegistry operationRegistry;
private final ConversationUtil conversationUtil;
private final OAuthValidator validator;
/** Holds incoming operation requests. */
private List<OperationRequest> operations;
public BaseApiServlet(RobotSerializer robotSerializer,
EventDataConverterManager converterManager, WaveletProvider waveletProvider,
OperationServiceRegistry operationRegistry, ConversationUtil conversationUtil,
OAuthValidator validator) {
this.robotSerializer = robotSerializer;
this.converterManager = converterManager;
this.waveletProvider = waveletProvider;
this.conversationUtil = conversationUtil;
this.operationRegistry = operationRegistry;
this.validator = validator;
}
/**
* Validates OAUTH and executes operations.
*
* @param req the request.
* @param resp the response.
* @param message the OAUTH message.
* @param accessor the OAUTH accessor.
* @param participant the author for which to perform the robot operations.
* @throws IOException if encountered errors during writing of a response.
*/
protected final void processOpsRequest(HttpServletRequest req, HttpServletResponse resp, OAuthMessage message,
OAuthAccessor accessor, ParticipantId participant) throws IOException {
try {
validator.validateMessage(message, accessor);
} catch (OAuthException e) {
LOG.info("The message does not conform to OAuth", e);
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
} catch (URISyntaxException e) {
LOG.info("The message URL is invalid", e);
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String apiRequest;
try {
// message.readBodyAsString() doesn't work due to a NPE in the OAuth
// libraries.
BufferedReader reader = req.getReader();
apiRequest = reader.readLine();
} catch (IOException e) {
LOG.warning("Unable to read the incoming request", e);
throw e;
}
LOG.info("Received the following Json: " + apiRequest);
try {
operations = robotSerializer.deserializeOperations(apiRequest);
} catch (InvalidRequestException e) {
LOG.info("Unable to parse Json to list of OperationRequests: " + apiRequest);
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Unable to parse Json to list of OperationRequests: " + apiRequest);
return;
}
// Create an unbound context.
ProtocolVersion version = OperationUtil.getProtocolVersion(operations);
OperationContextImpl context = new OperationContextImpl(
waveletProvider, converterManager.getEventDataConverter(version), conversationUtil);
executeOperations(context, operations, participant);
handleResults(context, resp, version);
}
/**
* Executes operations in the given context.
*
* @param context the context to perform the operations in.
* @param operations the operations to perform.
* @param author the author for which to perform the robot operations.
*/
private void executeOperations(
OperationContext context, List<OperationRequest> operations, ParticipantId author) {
for (OperationRequest operation : operations) {
OperationUtil.executeOperation(operation, operationRegistry, context, author);
}
}
/**
* Handles an {@link OperationResults} by submitting the deltas that are
* generated and writing a response to the robot.
*
* @param results the results of the operations performed.
* @param resp the servlet to write the response in.
* @param version the version of the protocol to use for writing a response.
* @throws IOException if the response can not be written.
*/
private void handleResults(
OperationResults results, HttpServletResponse resp, ProtocolVersion version)
throws IOException {
OperationUtil.submitDeltas(results, waveletProvider, LOGGING_REQUEST_LISTENER);
// Ensure that responses are returned in the same order as corresponding
// requests.
LinkedList<JsonRpcResponse> responses = Lists.newLinkedList();
for (OperationRequest operation : operations) {
String opId = operation.getId();
JsonRpcResponse response = results.getResponses().get(opId);
responses.addLast(response);
}
String jsonResponse =
robotSerializer.serialize(responses, GsonFactory.JSON_RPC_RESPONSE_LIST_TYPE, version);
LOG.info("Returning the following Json: " + jsonResponse);
// Write the response back through the HttpServlet
try {
resp.setContentType(JSON_CONTENT_TYPE);
PrintWriter writer = resp.getWriter();
writer.append(jsonResponse);
writer.flush();
resp.setStatus(HttpServletResponse.SC_OK);
} catch (IOException e) {
LOG.severe("IOException during writing of a response", e);
throw e;
}
}
}