Package org.jboss.errai.bus.client

Source Code of org.jboss.errai.bus.client.ClientMessageBusImpl

/*
* Copyright 2009 JBoss, a divison Red Hat, 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.jboss.errai.bus.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style;
import com.google.gwt.http.client.*;
import com.google.gwt.user.client.ui.*;
import org.jboss.errai.bus.client.ext.ExtensionsLoader;
import org.jboss.errai.bus.client.json.JSONUtilCli;
import org.jboss.errai.bus.client.protocols.BusCommands;
import org.jboss.errai.bus.client.protocols.MessageParts;

import java.util.*;

import static org.jboss.errai.bus.client.CommandMessage.create;
import static org.jboss.errai.bus.client.json.JSONUtilCli.decodePayload;
import static org.jboss.errai.bus.client.json.JSONUtilCli.encodeMap;
import static org.jboss.errai.bus.client.protocols.BusCommands.RemoteSubscribe;
import static org.jboss.errai.bus.client.protocols.MessageParts.ReplyTo;
import static org.jboss.errai.bus.client.protocols.MessageParts.Subject;

/**
* The default client <tt>MessageBus</tt> implementation.  This bus runs in the browser and automatically federates
* with the server immediately upon initialization.
*/
public class ClientMessageBusImpl implements ClientMessageBus {
    private static final String SERVICE_ENTRY_POINT = "erraiBus";

    private List<SubscribeListener> onSubscribeHooks = new ArrayList<SubscribeListener>();
    private List<UnsubscribeListener> onUnsubscribeHooks = new ArrayList<UnsubscribeListener>();

    private final RequestBuilder sendBuilder;
    private final RequestBuilder recvBuilder;

    private final Map<String, List<Object>> subscriptions = new HashMap<String, List<Object>>();

    private final Queue<String> outgoingQueue = new LinkedList<String>();
    private boolean transmitting = false;

    private Map<String, Set<Object>> registeredInThisSession = new HashMap<String, Set<Object>>();

    private ArrayList<Runnable> postInitTasks = new ArrayList<Runnable>();

    private boolean initialized = false;

    public ClientMessageBusImpl() {
        (sendBuilder = new RequestBuilder(
                RequestBuilder.POST,
                URL.encode(SERVICE_ENTRY_POINT)
        )).setHeader("Connection", "Keep-Alive");

        (recvBuilder = new RequestBuilder(
                RequestBuilder.GET,
                URL.encode(SERVICE_ENTRY_POINT)
        )).setHeader("Connection", "Keep-Alive");

        init();
    }

    public void unsubscribeAll(String subject) {
        if (subscriptions.containsKey(subject)) {
            for (Object o : subscriptions.get(subject)) {
                _unsubscribe(o);
            }

            fireAllUnSubcribeListener(subject);
        }
    }

    public void subscribe(final String subject, final MessageCallback callback) {
        fireAllSubcribeListener(subject);

        MessageCallback dispatcher = new MessageCallback() {
            public void callback(CommandMessage message) {
                try {
                    callback.callback(message);
                }
                catch (Exception e) {
                    showError("Receiver '" + subject + "' threw an exception", decodeCommandMessage(message), e);
                }
            }
        };

        addSubscription(subject, _subscribe(subject, dispatcher, null));
    }

    private void fireAllSubcribeListener(String subject) {
        Iterator<SubscribeListener> iter = onSubscribeHooks.iterator();
        SubscriptionEvent evt = new SubscriptionEvent(false, "InBrowser", subject);

        while (iter.hasNext()) {
            iter.next().onSubscribe(evt);
            if (evt.isDisposeListener()) {
                iter.remove();
                evt.setDisposeListener(false);
            }
        }
    }

    private void fireAllUnSubcribeListener(String subject) {
        Iterator<UnsubscribeListener> iter = onUnsubscribeHooks.iterator();
        SubscriptionEvent evt = new SubscriptionEvent(false, "InBrowser", subject);

        while (iter.hasNext()) {
            iter.next().onUnsubscribe(evt);
            if (evt.isDisposeListener()) {
                iter.remove();
                evt.setDisposeListener(false);
            }
        }
    }

    private static int conversationCounter = 0;

    /**
     * Have a single two-way conversation
     *
     * @param message  -
     * @param callback -
     */
    public void conversationWith(final CommandMessage message, final MessageCallback callback) {
        final String tempSubject = "temp:Conversation:" + (++conversationCounter);

        message.set(ReplyTo, tempSubject);

        subscribe(tempSubject, new MessageCallback() {
            public void callback(CommandMessage message) {
                unsubscribeAll(tempSubject);
                callback.callback(message);
            }
        });

        send(message);
    }

    public void sendGlobal(CommandMessage message) {
        send(message);
    }

    public void send(String subject, CommandMessage message) {
        try {
            send(subject, message.getParts());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void send(final String subject, final Map<String, Object> message) {
        message.put("ToSubject", subject);
        if (!initialized) {
            postInitTasks.add(new Runnable() {
                public void run() {
                    store(subject, encodeMap(message));
                }
            });
        } else {
            store(subject, encodeMap(message));
        }
    }

    public void send(String subject, Enum commandType) {
        send(subject, create(commandType));
    }

    public void send(CommandMessage message) {
        if (message.hasPart(MessageParts.ToSubject)) {
            send(message.get(String.class, MessageParts.ToSubject), message);
        } else {
            throw new RuntimeException("Cannot send message using this method" +
                    " if the message does not contain a ToSubject field.");
        }
    }

    public void enqueueForRemoteTransmit(CommandMessage message) {
        outgoingQueue.add(encodeMap(message.getParts()));
        sendAll();
    }

    private void addSubscription(String subject, Object reference) {
        if (!subscriptions.containsKey(subject)) {
            subscriptions.put(subject, new ArrayList<Object>());
        }

        if (registeredInThisSession != null && !registeredInThisSession.containsKey(subject)) {
            registeredInThisSession.put(subject, new HashSet<Object>());
        }

        subscriptions.get(subject).add(reference);
        if (registeredInThisSession != null) registeredInThisSession.get(subject).add(reference);
    }

    public boolean isSubscribed(String subject) {
        return subscriptions.containsKey(subject);
    }

    public Map<String, Set<Object>> getCapturedRegistrations() {
        return registeredInThisSession;
    }

    public void beginCapture() {
        registeredInThisSession = new HashMap<String, Set<Object>>();
    }

    public void endCapture() {
        registeredInThisSession = null;
    }

    public void unregisterAll(Map<String, Set<Object>> all) {
        for (Map.Entry<String, Set<Object>> entry : all.entrySet()) {
            for (Object o : entry.getValue()) {
                subscriptions.get(entry.getKey()).remove(o);
                _unsubscribe(o);
            }

            if (subscriptions.get(entry.getKey()).isEmpty()) {
                fireAllUnSubcribeListener(entry.getKey());
            }
        }
    }

    private com.google.gwt.user.client.Timer sendTimer;

    private void sendAll() {
        if (!initialized) {
            return;
        }
        if (transmitting) {
            if (sendTimer == null) {
                sendTimer = new com.google.gwt.user.client.Timer() {
                    int timeout = 0;

                    @Override
                    public void run() {
                        if (outgoingQueue.isEmpty()) {
                            cancel();
                        }

                        sendAll();

                        /**
                         * If this fails 20 times then we stop blocking
                         * progress and allow more messages to flow.
                         */
                        if (++timeout > 20) {
                            transmitting = false;
                        }
                    }
                };
                sendTimer.scheduleRepeating(75);
            }

            return;
        } else if (sendTimer != null) {
            sendTimer.cancel();
            sendTimer = null;
        }

        int transmissionSize = outgoingQueue.size();

        StringBuffer outgoing = new StringBuffer();
        for (int i = 0; i < transmissionSize; i++) {
            outgoing.append(outgoingQueue.poll());

            if ((i + 1) < transmissionSize) {
                outgoing.append("||");
            }
        }

        if (transmissionSize != 0) transmitRemote(outgoing.toString());
    }

    private void transmitRemote(String message) {
        if (message == null) return;

        try {
            transmitting = true;

            sendBuilder.sendRequest(message, new RequestCallback() {
                public void onResponseReceived(Request request, Response response) {
                    transmitting = false;

                    /**
                     * If the server bus returned us some client-destined messages
                     * in response to our send, handle them now.
                     */
                    try {
                        procIncomingPayload(response);
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                        showError("Problem decoding incoming message:", response.getText(), e);
                    }

                    sendAll();
                }

                public void onError(Request request, Throwable exception) {
                    showError("Failed to communicate with remote bus", "", exception);
                    transmitting = false;
                }
            });
        }
        catch (Exception e) {
            transmitting = false;
            e.printStackTrace();
        }
    }

    public void init() {
        init(null);
    }

    public void init(final HookCallback callback) {
        final MessageBus self = this;

        subscribe("ClientBus", new MessageCallback() {
            public void callback(CommandMessage message) {
                switch (BusCommands.valueOf(message.getCommandType())) {
                    case RemoteSubscribe:
                        subscribe(message.get(String.class, Subject), new MessageCallback() {
                            public void callback(CommandMessage message) {
                                enqueueForRemoteTransmit(message);
                            }
                        });
                        break;

                    case RemoteUnsubscribe:
                        unsubscribeAll(message.get(String.class, Subject));
                        break;

                    case FinishStateSync:
                        for (String s : subscriptions.keySet()) {
                            if (s.startsWith("local:")) continue;

                            create(RemoteSubscribe)
                                    .toSubject("ServerBus")
                                    .set(Subject, s)
                                    .sendNowWith(self);
                        }

                        // Don't use an iterator here -- potential for concurrent
                        // modifications!
                        //noinspection ForLoopReplaceableByForEach
                        for (int i = 0; i < postInitTasks.size(); i++) {
                            postInitTasks.get(i).run();
                        }

                        initialized = true;

                        break;
                }
            }
        });

        subscribe("ClientBusErrors", new MessageCallback() {
            public void callback(CommandMessage message) {
                showError(message.get(String.class, "ErrorMessage"),
                        message.get(String.class, "AdditionalDetails"), null);
            }
        });

        addSubscribeListener(new SubscribeListener() {
            public void onSubscribe(SubscriptionEvent event) {
                if (event.getSubject().startsWith("local:")) {
                    return;
                }
                create(RemoteSubscribe)
                        .toSubject("ServerBus")
                        .set(Subject, event.getSubject())
                        .sendNowWith(self);
            }
        });

        /**
         * ... also send RemoteUnsubscribe signals.
         */

        addUnsubscribeListener(new UnsubscribeListener() {
            public void onUnsubscribe(SubscriptionEvent event) {
                create(BusCommands.RemoteUnsubscribe)
                        .toSubject("ServerBus")
                        .set(Subject, event.getSubject())
                        .sendNowWith(self);
            }
        });


        String initialMessage = "{\"CommandType\":\"ConnectToQueue\",\"ToSubject\":\"ServerBus\"}";

        /**
         * Send initial message to connect to the queue, to establish an HTTP session. Otherwise, concurrent
         * requests will result in multiple sessions being creatd.  Which is bad.  Avoid this at all costs.
         * Please.
         */
        try {
            sendBuilder.sendRequest(initialMessage, new RequestCallback() {
                public void onResponseReceived(Request request, Response response) {
                    try {
                        procIncomingPayload(response);
                    }
                    catch (Exception e) {
                        return;
                    }
                    initializeMessagingBus(callback);
                }

                public void onError(Request request, Throwable exception) {
                    exception.printStackTrace();
                }
            });
        }
        catch (RequestException e) {
            //todo: handle this.
        }
    }

    public boolean isInitialized() {
        return initialized;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    private void initializeMessagingBus(final HookCallback initCallback) {
        final SimplePanel heartBeat = new SimplePanel();
        final HTML hBtext = new HTML("*Heartbeat*");
        hBtext.getElement().getStyle().setProperty("color", "red");

        heartBeat.add(hBtext);

        Style s = heartBeat.getElement().getStyle();
        s.setProperty("position", "absolute");
        s.setProperty("left", "300");
        s.setProperty("top", "10");

        heartBeat.setVisible(false);

        RootPanel.get().add(heartBeat);

        final com.google.gwt.user.client.Timer incoming = new com.google.gwt.user.client.Timer() {
            boolean block = false;

            @Override
            public void run() {
                if (block) {
                    scheduleRepeating(25);
                    return;
                }

                block = true;
                try {
                    recvBuilder.sendRequest(null,
                            new RequestCallback() {
                                public void onError(Request request, Throwable throwable) {
                                    block = false;
                                    showError("Communication Error", "None", throwable);
                                    schedule(1);
                                }

                                public void onResponseReceived(Request request, Response response) {
                                    block = false;
                                   
                                    try {
                                        procIncomingPayload(response);
                                        schedule(1);
                                    }
                                    catch (Exception e) {
                                        showError("Errai MessageBus Disconnected Due to Fatal Error", response.getText(), e);
                                    }
                                }
                            }
                    );
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
                block = false;
            }
        };

        final MessageBus bus = this;

        final com.google.gwt.user.client.Timer outerTimer = new com.google.gwt.user.client.Timer() {
            @Override
            public void run() {
                incoming.scheduleRepeating(1000);
                //  incoming.scheduleRepeating((60 * 45) * 1000);

                ExtensionsLoader loader = GWT.create(ExtensionsLoader.class);
                loader.initExtensions(bus);
            }
        };

        outerTimer.schedule(10);
    }

    public void addPostInitTask(Runnable run) {
        postInitTasks.add(run);
    }

    public void addGlobalListener(MessageListener listener) {
    }

    public void send(CommandMessage message, boolean fireListeners) {
        send(message);
    }

    public void send(String subject, CommandMessage message, boolean fireListener) {
        send(subject, message);
    }

    public void addSubscribeListener(SubscribeListener listener) {
        this.onSubscribeHooks.add(listener);
    }

    public void addUnsubscribeListener(UnsubscribeListener listener) {
        this.onUnsubscribeHooks.add(listener);
    }

    private native static void _unsubscribe(Object registrationHandle) /*-{
         $wnd.PageBus.unsubscribe(registrationHandle);
     }-*/;

    private native static Object _subscribe(String subject, MessageCallback callback,
                                            Object subscriberData) /*-{
          return $wnd.PageBus.subscribe(subject, null,
                  function (subject, message, subcriberData) {
                     callback.@org.jboss.errai.bus.client.MessageCallback::callback(Lorg/jboss/errai/bus/client/CommandMessage;)(@org.jboss.errai.bus.client.json.JSONUtilCli::decodeCommandMessage(Ljava/lang/Object;)(message))
                  },
                  null);
     }-*/;

    public static void store(String subject, Object value) {
        try {
            _store(subject, value);
        }
        catch (Exception e) {
            showError("Error sending data to client bus for '" + subject + "'", "Value:" + value, e);
        }
    }

    public native static void _store(String subject, Object value) /*-{
          $wnd.PageBus.store(subject, value);
     }-*/;

    private static String decodeCommandMessage(CommandMessage msg) {
        StringBuffer decode = new StringBuffer(
                "<table><thead style='font-weight:bold;'><tr><td>Field</td><td>Value</td></tr></thead><tbody>");

        for (Map.Entry<String, Object> entry : msg.getParts().entrySet()) {
            decode.append("<tr><td>").append(entry.getKey()).append("</td><td>").append(entry.getValue()).append("</td></tr>");
        }
        decode.append("</tbody></table>");

        return decode.toString();
    }

    private static void showError(String message, String additionalDetails, Throwable e) {
        DialogBox errorDialog = new DialogBox();

        StringBuffer buildTrace = new StringBuffer("<tt>");
        buildTrace.append(e.getClass().getName()).append(": ").append(e.getMessage()).append("<br/>");
        for (StackTraceElement ste : e.getStackTrace()) {
            buildTrace.append(ste.toString()).append("<br/>");
        }

        VerticalPanel panel = new VerticalPanel();
        panel.getElement().getStyle().setProperty("border", "1px");
        panel.getElement().getStyle().setProperty("borderStyle", "solid");
        panel.getElement().getStyle().setProperty("borderColor", "black");
        panel.getElement().getStyle().setProperty("backgroundColor", "lightgrey");

        panel.add(new HTML("<strong style='background:red;color:white;'>" + message + "</strong>"));

        ScrollPanel scrollPanel = new ScrollPanel();
        scrollPanel.add(new HTML(buildTrace.toString() + "<br/><strong>Additional Details:</strong>" + additionalDetails + "</tt>"));
        scrollPanel.setAlwaysShowScrollBars(true);
        scrollPanel.setHeight("500px");

        panel.add(scrollPanel);

        errorDialog.add(panel);

        errorDialog.center();
        errorDialog.show();
    }

    private static void procIncomingPayload(Response response) throws Exception {
        try {
            for (Message m : decodePayload(response.getText())) {
                store(m.getSubject(), m.getMessage());
            }
        }
        catch (Exception t) {
            showError("Error Receiving Message", response.getText(), t);
            throw t;
        }
    }
}
TOP

Related Classes of org.jboss.errai.bus.client.ClientMessageBusImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.