/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.api; import java.nio.channels.ScatteringByteChannel; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CountDownLatch; import divconq.bus.Message; import divconq.bus.MessageUtil; import divconq.hub.Hub; import divconq.lang.TimeoutPlan; import divconq.lang.op.FuncCallback; import divconq.lang.op.OperationCallback; import divconq.lang.op.OperationContext; import divconq.lang.op.UserContext; import divconq.script.StackEntry; import divconq.struct.FieldStruct; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.xml.XElement; // TODO make some of the properties accessible via RecordStruct fields for dcScript abstract public class ApiSession extends RecordStruct implements AutoCloseable { static public ApiSession createLocalSession(String domain) { return Hub.instance.createLocalApiSession(domain); } static public ApiSession createSessionFromConfig(String name) { return Hub.instance.createApiSession(name); } protected String sessionid = null; //protected String sessionKey = null; protected String hubid = null; protected UserContext user = null; protected ReplyService replies = new ReplyService(); public Message lastResult = null; public ReplyService getReplyService() { return this.replies; } public Message getLastResult() { return this.lastResult; } abstract public void init(XElement config); public void receiveMessage(Message msg) { // we need to restore/set the local operation context if anything is done here. // don't use the messages op context, it only applies within its own matrix // at least for starters let's set to guest OperationContext.useNewGuest(); System.out.println("Got a message for ApiSession, not sure what to do with it!\n\n" + msg); // TODO else look at other services published through this session (raise message in event to API) // push message back out to CoreApi } public UserContext getUser() { return this.user; } public String getSessionId() { return this.sessionid; } public String getHubId() { return this.hubid; } /* public String getSessionKey() { return this.sessionKey; } */ abstract public void sendForgetMessage(Message msg); public Message sendMessage(Message msg) { return this.sendMessage(msg, TimeoutPlan.Regular); } public Message sendMessage(Message msg, TimeoutPlan timeoutPlan) { this.lastResult = null; final CountDownLatch latch = new CountDownLatch(1); this.sendMessage(msg, new ServiceResult(timeoutPlan) { @Override public void callback() { ApiSession.this.lastResult = this.getResult(); latch.countDown(); } }); try { latch.await(); } catch (InterruptedException x) { this.lastResult = MessageUtil.errorTr(445, x); } return this.lastResult; } abstract public void sendMessage(Message msg, ServiceResult callback); public void allocDataChannel(String title, final FuncCallback<String> callback) { Message msg = new Message("Session", "DataChannel", "Allocate", new RecordStruct(new FieldStruct("Title", title))); this.sendMessage(msg, new ServiceResult() { @Override public void callback() { if (!this.hasErrors()) callback.setResult(this.getBodyAsRec().getFieldAsString("ChannelId")); callback.complete(); } }); } public void freeDataChannel(String channelid, final OperationCallback callback) { Message msg = new Message("Session", "DataChannel", "Free", new RecordStruct(new FieldStruct("ChannelId", channelid))); this.sendMessage(msg, new ServiceResult() { @Override public void callback() { callback.complete(); } }); } public void establishDataStream(String title, String mode, Message streamRequest, final FuncCallback<RecordStruct> callback) { Message msg = new Message("Session", "DataChannel", "Establish", new RecordStruct( new FieldStruct("Title", title), new FieldStruct("Mode", mode), new FieldStruct("StreamRequest", streamRequest) )); this.sendMessage(msg, new ServiceResult() { @Override public void callback() { if (!this.hasErrors()) callback.setResult(this.getBodyAsRec()); callback.complete(); } }); } public void bindSourceChannel(String channelid, RecordStruct addressing, final OperationCallback callback) { // TODO validate addressing RecordStruct body = new RecordStruct( new FieldStruct("ChannelId", channelid), new FieldStruct("Mode", "Source"), new FieldStruct("Hub", addressing.getFieldAsAny("Hub")), new FieldStruct("Session", addressing.getFieldAsAny("Session")), new FieldStruct("Channel", addressing.getFieldAsAny("Channel")) ); Message msg = new Message("Session", "DataChannel", "Bind", body); this.sendMessage(msg, new ServiceResult() { @Override public void callback() { callback.complete(); } }); } public void bindDestChannel(String channelid, RecordStruct addressing, final OperationCallback callback) { // TODO validate addressing RecordStruct body = new RecordStruct( new FieldStruct("ChannelId", channelid), new FieldStruct("Mode", "Destination"), new FieldStruct("Hub", addressing.getFieldAsAny("Hub")), new FieldStruct("Session", addressing.getFieldAsAny("Session")), new FieldStruct("Channel", addressing.getFieldAsAny("Channel")) ); Message msg = new Message("Session", "DataChannel", "Bind", body); this.sendMessage(msg, new ServiceResult() { @Override public void callback() { callback.complete(); } }); } abstract public void sendStream(ScatteringByteChannel in, long size, long offset, String channelid, OperationCallback or); abstract public void receiveStream(WritableByteChannel out, long size, long offset, String channelid, OperationCallback callback); abstract public void abortStream(String channelid); public void thawContext(Message result) { if (result == null) return; RecordStruct info = result.getFieldAsRecord("Body"); if (info == null) return; this.user = UserContext.allocate(info); //this.sessionKey = info.getFieldAsString("SessionKey"); this.sessionid = info.getFieldAsString("SessionId"); this.hubid = this.sessionid.substring(0, this.sessionid.indexOf('_')); } public void clearToGuest() { this.user = UserContext.allocateGuest(); } public boolean startSession() { return this.startSession(null); } public boolean startSession(String user, String pass) { return this.startSession(new RecordStruct( new FieldStruct("Username", user), new FieldStruct("Password", pass) )); } /* public boolean startSession(String user, String pass, String code) { return this.startSession(new RecordStruct( new FieldStruct("Username", user), new FieldStruct("Password", pass), new FieldStruct("ConfirmationCode", code) )); } */ public boolean startSessionAsGuest() { return this.startSession(null); } public boolean startSession(RecordStruct creds) { // new creds means new user, start as guest if (creds != null) this.clearToGuest(); Message msg = new Message(); msg.setField("Service", "Session"); msg.setField("Feature", "Control"); msg.setField("Op", "Start"); if (creds != null) msg.setField("Credentials", creds); Message rmsg = this.sendMessage(msg); if (rmsg == null) return false; this.thawContext(rmsg); // a real user or a guest will both be verified return !rmsg.hasErrors() && this.getUser().isVerified(); } public void stop() { Message msg = new Message(); msg.setField("Service", "Session"); msg.setField("Feature", "Control"); msg.setField("Op", "Stop"); this.sendForgetMessage(msg); this.clearToGuest(); this.stopped(); } abstract public void stopped(); public Collection<Message> checkInBox() { Message msg = new Message(); msg.setField("Service", "Session"); msg.setField("Feature", "Control"); msg.setField("Op", "CheckInBox"); Message rmsg = this.sendMessage(msg); if (rmsg.hasErrors()) return null; ListStruct mlist = rmsg.getFieldAsList("Body"); if (mlist == null) return null; ArrayList<Message> msgs = new ArrayList<Message>(); for (Struct m : mlist.getItems()) { if (m instanceof RecordStruct) msgs.add(MessageUtil.fromRecord((RecordStruct) m)); } return msgs; } @Override public void operation(StackEntry stack, XElement code) { if ("Stop".equals(code.getName())) { this.stop(); stack.resume(); return; } super.operation(stack, code); } @Override public void close() { this.stop(); } }