/**
* Copyright 2009 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.wave.examples.fedone.agents.probey;
import com.google.common.collect.ImmutableList;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.security.*;
import org.waveprotocol.wave.examples.fedone.agents.agent.AbstractAgent;
import org.waveprotocol.wave.examples.fedone.agents.agent.AgentConnection;
import org.waveprotocol.wave.examples.fedone.common.DocumentConstants;
import org.waveprotocol.wave.examples.fedone.util.Log;
import org.waveprotocol.wave.examples.fedone.waveclient.common.ClientUtils;
import org.waveprotocol.wave.examples.fedone.waveclient.common.ClientWaveView;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.BufferedDocOp;
import org.waveprotocol.wave.model.document.operation.DocInitializationCursor;
import org.waveprotocol.wave.model.document.operation.impl.InitializationCursorAdapter;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.operation.wave.AddParticipant;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletDocumentOperation;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.WaveletData;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* The probey agent provides a web interface for remote applications to trigger operations against a
* fedone waveserver. It allows easy testing of Federation.
*
* @author arb@google.com (Anthony Baxter)
*/
public class Probey extends AbstractAgent {
private static final Log LOG = Log.get(Probey.class);
private ParticipantId userId;
/**
* Constructor.
*
* @param connection the agent's connection to the server.
* @param userAtDomain the user@domain name of the robot
*/
private Probey(AgentConnection connection, String userAtDomain) {
super(connection);
this.userId = new ParticipantId(userAtDomain);
}
/**
* Adds a blip to the given wave.
*
* @param waveId the ID of the wave
* @param blipText the text to include in the blip
* @return the new blip ID
*/
private String addBlip(String waveId, String blipText) {
ClientWaveView wave = getWave(WaveId.deserialise(waveId));
if (wave == null) {
throw new IllegalArgumentException("NOT FOUND");
}
WaveletData convRoot = ClientUtils.getConversationRoot(wave);
BufferedDocOp manifest = convRoot.getDocuments().get(DocumentConstants.MANIFEST_DOCUMENT_ID);
String newDocId = getNewDocumentId();
WaveletDelta delta = ClientUtils.createAppendBlipDelta(manifest, userId, newDocId, blipText);
sendAndAwaitWaveletDelta(convRoot.getWaveletName(), delta);
return newDocId;
}
/**
* Adds a participant to a wave.
*
* @param waveId the ID of the wave
* @param name the user to add to the wave
*/
public void addUser(String waveId, String name) {
ParticipantId addId = new ParticipantId(name);
ClientWaveView wave = getWave(WaveId.deserialise(waveId));
if (wave == null) {
throw new IllegalArgumentException("NOT FOUND");
}
WaveletData convRoot = ClientUtils.getConversationRoot(wave);
AddParticipant addUserOp = new AddParticipant(addId);
sendAndAwaitWaveletDelta(convRoot.getWaveletName(), new WaveletDelta(userId,
ImmutableList.of(
addUserOp)));
}
/**
* Creates a new wave.
*
* @return the new wave's ID
*/
public String createNewWave() {
ClientWaveView waveView = newWave();
return waveView.getWaveId().serialise();
}
/**
* Fetches the blips in a wave.
*
* @param waveId the ID of the wave
* @return a simply formatted text version of the wave
*/
private String renderBlips(String waveId) {
ClientWaveView wave = getWave(WaveId.deserialise(waveId));
if (wave == null) {
throw new IllegalArgumentException("NOT FOUND");
}
WaveletData convRoot = ClientUtils.getConversationRoot(wave);
BufferedDocOp manifest = convRoot.getDocuments()
.get(DocumentConstants.MANIFEST_DOCUMENT_ID);
final Map<String, BufferedDocOp> documentMap =
ClientUtils.getConversationRoot(wave).getDocuments();
if (manifest == null) {
throw new IllegalArgumentException("MANIFEST MISSING");
}
final StringBuilder builder = new StringBuilder();
manifest.apply(new InitializationCursorAdapter((
new DocInitializationCursor() {
@Override
public void elementStart(String type, Attributes attrs) {
if (type.equals(DocumentConstants.BLIP)) {
if (attrs.containsKey(DocumentConstants.BLIP_ID)) {
BufferedDocOp document =
documentMap.get(attrs.get(DocumentConstants.BLIP_ID));
if (document != null) {
// A nonexistent document is indistinguishable from
// the empty document, so document == null is not necessarily an error.
builder.append("Blip: ");
builder.append(attrs.get(DocumentConstants.BLIP_ID));
builder.append("\nContent: ");
document.apply(new InitializationCursorAdapter(new DocInitializationCursor() {
@Override
public void characters(String chars) {
builder.append(chars);
}
@Override
public void annotationBoundary(AnnotationBoundaryMap map) {
}
@Override
public void elementStart(String type, Attributes attrs) {
}
@Override
public void elementEnd() {
}
}));
builder.append("\n");
}
}
}
}
@Override
public void annotationBoundary(AnnotationBoundaryMap map) {
}
@Override
public void characters(String chars) {
}
@Override
public void elementEnd() {
}
})));
return builder.toString();
}
@Override
public void onDocumentChanged(WaveletData wavelet,
WaveletDocumentOperation documentOperation) {
}
@Override
public void onParticipantAdded(WaveletData wavelet,
ParticipantId participant) {
}
@Override
public void onParticipantRemoved(WaveletData wavelet,
ParticipantId participant) {
}
@Override
public void onSelfAdded(WaveletData wavelet) {
}
@Override
public void onSelfRemoved(WaveletData wavelet) {
}
public static void main(String[] args) {
try {
if (args.length == 4) {
int port, httpport;
try {
port = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Must provide valid port.");
}
try {
httpport = Integer.parseInt(args[3]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Must provide valid http port.");
}
if (args[0].split("@").length != 2) {
throw new IllegalArgumentException("username must be in form user@domain");
}
Probey probey = new Probey(AgentConnection.newConnection(args[0],
args[1],
port),
args[0]);
Handler handler = new WebHandler(probey);
Server server = new Server(httpport);
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);
constraint.setRoles(new String[]{"probey"});
constraint.setAuthenticate(true);
ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setConstraint(constraint);
constraintMapping.setPathSpec("/*");
HashUserRealm userRealm = new HashUserRealm();
userRealm.setName("Probey");
userRealm.setConfig("./etc/probey/realm.properties");
SecurityHandler securityHandler = new SecurityHandler();
securityHandler.setUserRealm(userRealm);
securityHandler.setConstraintMappings(new ConstraintMapping[]{constraintMapping});
server.setUserRealms(new UserRealm[]{userRealm});
server.setHandlers(new Handler[]{securityHandler, handler});
server.start(); // spawns a new thread.
probey.run();
} else {
System.out
.println("usage: java Probey <username@domain> <fedone hostname>"
+ " <fedone port> <http port>");
}
} catch (Exception e) {
LOG.severe("Catastrophic failure", e);
System.exit(1);
}
System.exit(0);
}
static class WebHandler extends AbstractHandler {
private Probey probey;
public WebHandler(Probey probey) {
this.probey = probey;
}
@Override
public void handle(String s, HttpServletRequest request, HttpServletResponse response, int i)
throws IOException, ServletException {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
final String url = request.getRequestURI();
final PrintWriter writer = response.getWriter();
try {
if (url.equals("/new")) {
LOG.info("creating a new wave");
final String waveId = probey.createNewWave();
LOG.info("new wave id: " + waveId);
writer.println(waveId);
} else if (url.startsWith("/add/")) {
String[] parts = url.split("/");
if (parts.length != 4) {
throw new IllegalArgumentException("bad request");
}
LOG.info("adding user: " + parts[3] + " to wave: " + parts[2]);
probey.addUser(parts[2], parts[3]);
writer.println("OK");
} else if (url.startsWith("/addblip/")) {
String[] parts = url.split("/");
if (parts.length != 4) {
throw new IllegalArgumentException("bad request");
}
LOG.info("adding blip: " + parts[3] + " to wave: " + parts[2]);
writer.println(probey.addBlip(parts[2], parts[3]));
} else if (url.startsWith("/getblips/")) {
String[] parts = url.split("/");
if (parts.length != 3) {
throw new IllegalArgumentException("bad request");
}
LOG.info("getting blips for wave: " + parts[2]);
writer.println(probey.renderBlips(parts[2]));
} else {
response.getWriter().println("Unknown: " + url);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
} catch (IllegalArgumentException e) {
writer.println(e.getMessage());
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
((Request) request).setHandled(true);
}
}
}