Java tutorial
/* * Copyright (C) 2010 Stanford University * * 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 edu.stanford.junction.provider.irc; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.URI; import java.util.Observer; import java.util.List; import java.util.Observable; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.UUID; import org.json.JSONException; import org.json.JSONObject; import edu.stanford.junction.api.activity.ActivityScript; import edu.stanford.junction.api.activity.JunctionActor; import edu.stanford.junction.api.messaging.MessageHeader; import f00f.net.irc.martyr.InCommand; import f00f.net.irc.martyr.commands.MessageCommand; import f00f.net.irc.martyr.commands.NoticeCommand; import f00f.net.irc.martyr.commands.JoinCommand; import f00f.net.irc.martyr.services.AutoJoin; import f00f.net.irc.martyr.services.AutoRegister; import f00f.net.irc.martyr.services.AutoReconnect; import f00f.net.irc.martyr.services.AutoResponder; import f00f.net.irc.martyr.clientstate.ClientState; import f00f.net.irc.martyr.IRCConnection; import f00f.net.irc.martyr.util.FullNick; public class Junction extends edu.stanford.junction.Junction { private final URI mAcceptedInvitation; private final String mSession; private ActivityScript mActivityScript; private IRCConnection mConnection; private FullNick mFullNick; private String mNickname; private ClientState mClientState; private MessageMultiplexer mMultiplex = new MessageMultiplexer(); private MessageDemultiplexer mDemultiplex = new MessageDemultiplexer(); public static String JX_NS = "jx"; public static String JX_SYS_MSG = "jxsysmsg"; // Stores responses to distributed activity script requests. private LinkedBlockingQueue<JSONObject> scriptQ = new LinkedBlockingQueue<JSONObject>(); public static String makeIRCName(String name) { String s = String.valueOf(name.hashCode()); s = s.replace("-", "0"); s = "x" + s.substring(0, Math.min(s.length() - 1, 7)); return s; } public Junction(URI uri, ActivityScript script, final JunctionActor actor) { this.setActor(actor); mNickname = makeIRCName(actor.getActorID()); // User & name are not important for // our needs. String user = "jxuser"; String name = "jxuser"; mFullNick = new FullNick(mNickname + "!" + user + "@127.0.0.1"); mAcceptedInvitation = uri; mActivityScript = script; mSession = "jxsession-" + makeIRCName(uri.getPath().substring(1)); String host = uri.getHost(); int port = uri.getPort(); mClientState = new ClientState(); mConnection = new IRCConnection(mClientState); mConnection.addStateObserver(new JXStateObserver()); mConnection.addCommandObserver(new JXCommandObserver()); AutoJoin autoJoin = new AutoJoin(mConnection, "#" + mSession); AutoRegister autoReg = new AutoRegister(mConnection, mNickname, user, name); AutoReconnect autoRecon = new AutoReconnect(mConnection); AutoResponder autoRes = new AutoResponder(mConnection); autoRecon.go(host, port); } // This observer is updated whenever a command // is received by the irc connection. 'update' is // called from the irc thread. Therefore junction // is 'driven' (by way of triggerActorJoin // and triggerMessageReceived) from the irc thread. // // This is safe as long the irc thread is the ONLY // thread sending updates into junction. Additionally, // the client must be aware that JunctionActor's methods // will be called on the irc thread. // class JXCommandObserver implements Observer { public void update(Observable o, Object arg) { InCommand cmd = (InCommand) arg; if (cmd instanceof JoinCommand) { JoinCommand c = (JoinCommand) cmd; if (c.weJoined(mClientState)) { triggerActorJoin(mActivityScript == null || mActivityScript.isActivityCreator()); } } else if (cmd instanceof MessageCommand) { MessageCommand c = (MessageCommand) cmd; mDemultiplex.addFragment(c.getMessage(), c.getSource().getNick()); List<MessageDemultiplexer.CompleteMessage> msgs = mDemultiplex.drainCompleteMessages(); for (MessageDemultiplexer.CompleteMessage each : msgs) { String from = each.from; String src = each.msg; try { JSONObject obj = new JSONObject(src); if (obj.optBoolean("scriptRequest")) { handleScriptRequest(from, obj); return; } else if (obj.optBoolean("scriptResponse")) { scriptQ.put(obj); return; } else if (obj.has(NS_JX)) { JSONObject header = obj.optJSONObject(NS_JX); if (header.has("targetRole")) { String target = header.optString("targetRole"); if (!inLocalRoles(target)) return; } } MessageHeader header = new MessageHeader(Junction.this, obj, from); triggerMessageReceived(header, obj); } catch (Exception e) { System.err.println("Could not handle incoming message: " + src); } } } } } class JXStateObserver implements Observer { public void update(Observable o, Object arg) { System.out.println("State update: " + arg.toString()); } } protected void handleScriptRequest(String from, JSONObject req) { if (mActivityScript == null) return; try { JSONObject response = new JSONObject(); response.put("scriptResponse", true); response.put("requestId", req.optString("requestId")); response.put("script", mActivityScript.getJSON()); sendMessageToActor(from, response); } catch (JSONException e) { e.printStackTrace(System.err); } } private Boolean inLocalRoles(String target) { String[] roles = getActor().getRoles(); for (int i = 0; i < roles.length; i++) { if (roles[i].equals(target)) { return true; } } return false; } @Override public void disconnect() { mConnection.disconnect(); } @Override public URI getAcceptedInvitation() { return mAcceptedInvitation; } @Override public ActivityScript getActivityScript() { scriptQ.clear(); JSONObject req = new JSONObject(); String requestId = UUID.randomUUID().toString(); try { req.put("scriptRequest", "true"); req.put("requestId", requestId); sendMessageToSession(req); } catch (JSONException e) { e.printStackTrace(System.err); return null; } try { int maxTries = 5; long maxWaitPerTry = 2000L; for (int i = 0; i < maxTries; i++) { JSONObject response = scriptQ.poll(maxWaitPerTry, TimeUnit.MILLISECONDS); if (response == null) { return null; } else if (response.optString("requestId").equals(requestId)) { JSONObject script = response.optJSONObject("script"); return new ActivityScript(script); } } } catch (InterruptedException e) { return null; } return null; } @Override public URI getBaseInvitationURI() { try { return new URI("junction://localhost#irc"); } catch (Exception e) { return null; } } @Override public String getSessionID() { return mSession; } @Override public String getSwitchboard() { return mAcceptedInvitation.getHost(); } private void sendMsgTo(String msg, String to) { List<String> fragments = mMultiplex.divide(msg); for (String frag : fragments) { MessageCommand cmd = new MessageCommand(mFullNick, to, frag); mConnection.sendCommand(cmd); } } @Override public void doSendMessageToActor(String actorID, JSONObject message) { sendMsgTo(message.toString(), actorID); } @Override public void doSendMessageToRole(String role, JSONObject message) { JSONObject jx; if (message.has(NS_JX)) { jx = message.optJSONObject(NS_JX); } else { jx = new JSONObject(); try { message.put(NS_JX, jx); } catch (JSONException j) { } } try { jx.put("targetRole", role); } catch (Exception e) { } String msg = message.toString(); sendMsgTo(msg, "#" + mSession + "," + mNickname); } @Override public void doSendMessageToSession(JSONObject message) { String msg = message.toString(); sendMsgTo(msg, "#" + mSession + "," + mNickname); } }