All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dsa.iot.dslink.link.Requester Maven / Gradle / Ivy

There is a newer version: 0.24.2
Show newest version
package org.dsa.iot.dslink.link;

import org.dsa.iot.dslink.DSLink;
import org.dsa.iot.dslink.DSLinkHandler;
import org.dsa.iot.dslink.methods.Request;
import org.dsa.iot.dslink.methods.StreamState;
import org.dsa.iot.dslink.methods.requests.*;
import org.dsa.iot.dslink.methods.responses.*;
import org.dsa.iot.dslink.node.Node;
import org.dsa.iot.dslink.node.NodeManager;
import org.dsa.iot.dslink.node.NodePair;
import org.dsa.iot.dslink.node.SubscriptionManager;
import org.dsa.iot.dslink.node.value.SubscriptionValue;
import org.dsa.iot.dslink.util.Objects;
import org.dsa.iot.dslink.util.SubData;
import org.dsa.iot.dslink.util.handler.Handler;
import org.dsa.iot.dslink.util.json.JsonObject;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Handles incoming responses and outgoing requests.
 *
 * @author Samuel Grenier
 */
public class Requester extends Linkable {

    private final Map reqs;

    /**
     * Current request ID to send to the client
     */
    private final AtomicInteger currentReqID = new AtomicInteger();

    /**
     * Current subscription ID to send to the client
     */
    private final AtomicInteger currentSubID = new AtomicInteger();

    /**
     * Mapping of path->sid
     */
    private final Map subPaths = new ConcurrentHashMap<>();

    /**
     * Mapping of sid->path
     */
    private final Map subSids = new ConcurrentHashMap<>();

    /**
     * Mapping of sid->handler
     */
    private final Map> subUpdates = new ConcurrentHashMap<>();

    /**
     * Mapping of rid->response
     */
    private final Map invokeResponses = new HashMap<>();

    /**
     * Constructs a requester
     *
     * @param handler Handler for callbacks and data handling
     */
    public Requester(DSLinkHandler handler) {
        super(handler);
        reqs = new ConcurrentHashMap<>();
    }

    @SuppressWarnings("unused")
    public Map getSubscriptionPaths() {
        return Collections.unmodifiableMap(subPaths);
    }

    public Map getSubscriptionIDs() {
        return Collections.unmodifiableMap(subSids);
    }

    @SuppressWarnings("unused")
    public boolean isSubscribed(String path) {
        return subPaths.containsKey(path);
    }

    public Map> getSubscriptionHandlers() {
        return Collections.unmodifiableMap(subUpdates);
    }

    public void subscribe(String path,
                          Handler onUpdate) {
        SubData sub = new SubData(path, null);
        subscribe(sub, onUpdate);
    }

    public void subscribe(SubData path,
                          Handler onUpdate) {
        subscribe(Collections.singleton(path), onUpdate);
    }

    public void subscribe(Set paths,
                          Handler onUpdate) {
        if (paths == null) {
            throw new NullPointerException("paths");
        }
        subscribe(new SubscribeRequest(paths), onUpdate);
    }

    public void subscribe(SubscribeRequest req,
                          Handler onUpdate) {
        if (req == null) {
            throw new NullPointerException("req");
        }
        final Set paths = req.getPaths();
        Map subs = new HashMap<>();
        int min = currentSubID.getAndAdd(paths.size());
        int max = min + paths.size();
        Iterator it = paths.iterator();
        StringBuilder error = null;
        while (min < max && it.hasNext()) {
            try {
                SubData data = it.next();
                String path = data.getPath();
                subs.put(data, min);
                Integer prev = subPaths.put(path, min);
                if (prev != null) {
                    String err = "Path " + path + " already subscribed";
                    throw new RuntimeException(err);
                }
                subSids.put(min, path);
                if (onUpdate != null) {
                    subUpdates.put(min, onUpdate);
                }
                min++;
            } catch (IllegalArgumentException e) {
                if (error == null) {
                    error = new StringBuilder();
                }
                StringWriter writer = new StringWriter();
                e.printStackTrace(new PrintWriter(writer));
                error.append(writer.toString());
                error.append("\n\n");
            }
        }

        req.setSubSids(subs);
        RequestWrapper wrapper = new RequestWrapper(req);
        sendRequest(wrapper, currentReqID.incrementAndGet());
        if (error != null) {
            throw new RuntimeException(error.toString());
        }
    }

    public void unsubscribe(String path, Handler onResponse) {
        Set paths = new HashSet<>();
        paths.add(path);
        unsubscribe(paths, onResponse);
    }

    public void unsubscribe(Set paths, Handler onResponse) {
        if (paths == null) {
            throw new NullPointerException("paths");
        }
        List subs = new ArrayList<>();
        for (String path : paths) {
            path = NodeManager.normalizePath(path, true);
            Integer sid = subPaths.remove(path);
            if (sid != null) {
                subs.add(sid);
                subSids.remove(sid);
                subUpdates.remove(sid);
            }
        }
        UnsubscribeRequest req = new UnsubscribeRequest(subs);
        RequestWrapper wrapper = new RequestWrapper(req);
        wrapper.setUnsubHandler(onResponse);
        sendRequest(wrapper, currentReqID.incrementAndGet());
    }

    /**
     * Sends a request to the responder to close the given stream.
     *
     * @param rid Stream to close.
     * @param onResponse Response.
     */
    @SuppressWarnings("unused")
    public void closeStream(int rid, Handler onResponse) {
        CloseRequest req = new CloseRequest();
        RequestWrapper wrapper = new RequestWrapper(req);
        wrapper.setCloseHandler(onResponse);
        sendRequest(wrapper, rid);

        reqs.remove(rid);
        if (onResponse != null) {
            wrapper.getCloseHandler().handle(new CloseResponse(rid, null));
        }
    }

    /**
     * Sends an invocation request.
     *
     * @param request Invocation request.
     * @param onResponse Response.
     */
    public void invoke(InvokeRequest request, Handler onResponse) {
        RequestWrapper wrapper = new RequestWrapper(request);
        wrapper.setInvokeHandler(onResponse);
        sendRequest(wrapper);
    }

    /**
     * Sends a list request.
     *
     * @param request List request.
     * @param onResponse Response.
     */
    public void list(ListRequest request, Handler onResponse) {
        RequestWrapper wrapper = new RequestWrapper(request);
        wrapper.setListHandler(onResponse);
        sendRequest(wrapper);
    }

    /**
     * Sends a set request.
     *
     * @param request Set request.
     * @param onResponse Response.
     */
    public void set(SetRequest request, Handler onResponse) {
        RequestWrapper wrapper = new RequestWrapper(request);
        wrapper.setSetHandler(onResponse);
        sendRequest(wrapper);
    }

    /**
     * Sends a remove request.
     *
     * @param request Remove request.
     * @param onResponse Called when a response is received.
     */
    public void remove(RemoveRequest request, Handler onResponse) {
        RequestWrapper wrapper = new RequestWrapper(request);
        wrapper.setRemoveHandler(onResponse);
        sendRequest(wrapper);
    }

    /**
     * Sends a request to the client.
     *
     * @param wrapper Request to send to the client.
     */
    private void sendRequest(RequestWrapper wrapper) {
        int rid = currentReqID.incrementAndGet();
        sendRequest(wrapper, rid);
    }

    /**
     * Sends a request to the client with a given request ID.
     *
     * @param wrapper Request to send to the client
     * @param rid Request ID to use
     */
    private void sendRequest(RequestWrapper wrapper, int rid) {
        final DSLink link = getDSLink();
        if (link == null) {
            return;
        }
        Request request = wrapper.getRequest();
        JsonObject obj = new JsonObject();
        request.addJsonValues(obj);
        {
            obj.put("rid", rid);
            reqs.put(rid, wrapper);
        }
        obj.put("method", request.getName());
        link.getWriter().writeRequest(obj);
    }

    /**
     * Handles incoming responses.
     *
     * @param in Incoming response.
     */
    public void parse(final JsonObject in) {
        DSLink link = getDSLink();
        if (link == null) {
            return;
        }
        int rid = in.get("rid");
        if (rid == 0) {
            final SubscriptionUpdate update = new SubscriptionUpdate(this);
            Objects.getThreadPool().execute(new Runnable() {
                @Override
                public void run() {
                    update.populate(in);
                }
            });
            return;
        }
        RequestWrapper wrapper = reqs.get(rid);
        Request request = wrapper.getRequest();
        String method = request.getName();

        StreamState stream = StreamState.toEnum((String) in.get("stream"));
        if (stream == null) {
            stream = StreamState.OPEN;
        }
        final boolean closed = StreamState.CLOSED == stream;
        final NodeManager manager = link.getNodeManager();

        switch (method) {
            case "list":
                ListRequest listRequest = (ListRequest) request;
                Node node = manager.getNode(listRequest.getPath(), true).getNode();
                String path = node.getPath();
                SubscriptionManager subs = link.getSubscriptionManager();
                ListResponse resp = new ListResponse(link, subs, rid, node, path);
                resp.populate(in);
                if (wrapper.getListHandler() != null) {
                    wrapper.getListHandler().handle(resp);
                }
                break;
            case "set":
                SetRequest setRequest = (SetRequest) request;
                path = setRequest.getPath();
                manager.getNode(path, true);
                SetResponse setResponse = new SetResponse(rid, link, path);
                setResponse.populate(in);
                if (wrapper.getSetHandler() != null) {
                    wrapper.getSetHandler().handle(setResponse);
                }
                break;
            case "remove":
                RemoveRequest removeRequest = (RemoveRequest) request;
                NodePair pair = manager.getNode(removeRequest.getPath(), true);
                RemoveResponse removeResponse = new RemoveResponse(rid, pair);
                removeResponse.populate(in);
                if (wrapper.getRemoveHandler() != null) {
                    wrapper.getRemoveHandler().handle(removeResponse);
                }
                break;
            case "close":
                break;
            case "subscribe":
                SubscribeResponse subResp = new SubscribeResponse(rid, link);
                subResp.populate(in);
                break;
            case "unsubscribe":
                UnsubscribeResponse unsubResp = new UnsubscribeResponse(rid, link);
                unsubResp.populate(in);
                if (wrapper.getUnsubHandler() != null) {
                    wrapper.getUnsubHandler().handle(unsubResp);
                }
                break;
            case "invoke":
                InvokeRequest inReq = (InvokeRequest) request;
                path = inReq.getPath();
                manager.getNode(path, true);
                InvokeResponse inResp;
                synchronized (invokeResponses) {
                    switch (stream) {
                        case OPEN:
                            inResp = invokeResponses.get(rid);
                            break;
                        case INITIALIZED:
                            inResp = new InvokeResponse(link, rid, path);
                            invokeResponses.put(rid, inResp);
                            break;
                        case CLOSED:
                            inResp = invokeResponses.remove(rid);
                            break;
                        default:
                            inResp = null;
                    }
                    if (inResp == null) {
                        inResp = new InvokeResponse(link, rid, path);
                    }
                }
                inResp.populate(in);
                boolean invoke = false;
                if (inReq.waitForStreamClose()) {
                    if (closed) {
                        invoke = true;
                    }
                } else {
                    invoke = true;
                }
                if (invoke && wrapper.getInvokeHandler() != null) {
                    wrapper.getInvokeHandler().handle(inResp);
                }
                break;
            default:
                throw new RuntimeException("Unsupported method: " + method);
        }

        if (closed) {
            reqs.remove(rid);
        }
    }

    /**
     * Forcibly clears all subscriptions and handlers. This does not call
     * unsubscribe to the server.
     */
    public void clearSubscriptions() {
        subPaths.clear();
        subSids.clear();
        subUpdates.clear();
    }

    private static class RequestWrapper {

        private final Request request;

        private Handler closeHandler;
        private Handler invokeHandler;
        private Handler listHandler;
        private Handler removeHandler;
        private Handler setHandler;
        private Handler unsubHandler;

        public RequestWrapper(Request request) {
            this.request = request;
        }

        public Request getRequest() {
            return request;
        }

        public Handler getCloseHandler() {
            return closeHandler;
        }

        public void setCloseHandler(Handler closeHandler) {
            this.closeHandler = closeHandler;
        }

        public Handler getInvokeHandler() {
            return invokeHandler;
        }

        public void setInvokeHandler(Handler invokeHandler) {
            this.invokeHandler = invokeHandler;
        }

        public Handler getListHandler() {
            return listHandler;
        }

        public void setListHandler(Handler listHandler) {
            this.listHandler = listHandler;
        }

        public Handler getRemoveHandler() {
            return removeHandler;
        }

        public void setRemoveHandler(Handler removeHandler) {
            this.removeHandler = removeHandler;
        }

        public Handler getSetHandler() {
            return setHandler;
        }

        public void setSetHandler(Handler setHandler) {
            this.setHandler = setHandler;
        }

        public Handler getUnsubHandler() {
            return unsubHandler;
        }

        public void setUnsubHandler(Handler unsubHandler) {
            this.unsubHandler = unsubHandler;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy