Package com.ponysdk.ui.terminal

Source Code of com.ponysdk.ui.terminal.UIBuilder

/*
* Copyright (c) 2011 PonySDK
*  Owners:
*  Luciano Broussal  <luciano.broussal AT gmail.com>
*  Mathieu Barbier   <mathieu.barbier AT gmail.com>
*  Nicolas Ciaravola <nicolas.ciaravola.pro AT gmail.com>
*  WebSite:
*  http://code.google.com/p/pony-sdk/
*
* 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 com.ponysdk.ui.terminal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.ponysdk.ui.terminal.Dictionnary.APPLICATION;
import com.ponysdk.ui.terminal.Dictionnary.HANDLER;
import com.ponysdk.ui.terminal.Dictionnary.HISTORY;
import com.ponysdk.ui.terminal.Dictionnary.TYPE;
import com.ponysdk.ui.terminal.event.CommunicationErrorEvent;
import com.ponysdk.ui.terminal.event.HttpRequestSendEvent;
import com.ponysdk.ui.terminal.event.HttpResponseReceivedEvent;
import com.ponysdk.ui.terminal.exception.ServerException;
import com.ponysdk.ui.terminal.extension.AddonFactory;
import com.ponysdk.ui.terminal.extension.AddonList;
import com.ponysdk.ui.terminal.extension.PonyAddonList;
import com.ponysdk.ui.terminal.instruction.PTInstruction;
import com.ponysdk.ui.terminal.request.RequestBuilder;
import com.ponysdk.ui.terminal.ui.PTCookies;
import com.ponysdk.ui.terminal.ui.PTObject;
import com.ponysdk.ui.terminal.ui.PTStreamResource;

public class UIBuilder implements ValueChangeHandler<String>, UIService, HttpResponseReceivedEvent.Handler, HttpRequestSendEvent.Handler {

    private final static Logger log = Logger.getLogger(UIBuilder.class.getName());

    private static EventBus rootEventBus = new SimpleEventBus();

    private final UIFactory uiFactory = new UIFactory();
    private final Map<String, AddonFactory> addonByKey = new HashMap<String, AddonFactory>();
    private final Map<Long, PTObject> objectByID = new HashMap<Long, PTObject>();
    private final Map<UIObject, Long> objectIDByWidget = new HashMap<UIObject, Long>();
    private final Map<Long, UIObject> widgetIDByObjectID = new HashMap<Long, UIObject>();
    private final List<PTInstruction> stackedInstructions = new ArrayList<PTInstruction>();
    private final List<JSONObject> stackedErrors = new ArrayList<JSONObject>();

    private final Map<Long, JSONObject> incomingMessageQueue = new HashMap<Long, JSONObject>();

    private SimplePanel loadingMessageBox;
    private PopupPanel communicationErrorMessagePanel;
    private Timer timer;
    private int numberOfrequestInProgress = 0;

    private boolean updateMode;
    private boolean pendingClose;

    private RequestBuilder requestBuilder;

    private long lastReceived = -1;
    private long nextSent = 1;

    public static long sessionID;

    private CommunicationErrorHandler communicationErrorHandler;
    private final Map<String, JavascriptAddOnFactory> javascriptAddOnFactories = new HashMap<String, JavascriptAddOnFactory>();

    public UIBuilder() {
        History.addValueChangeHandler(this);

        final AddonList addonList = GWT.create(PonyAddonList.class);

        final List<AddonFactory> addonFactoryList = addonList.getAddonFactoryList();

        for (final AddonFactory addonFactory : addonFactoryList) {
            addonByKey.put(addonFactory.getSignature(), addonFactory);
        }

        rootEventBus.addHandler(HttpResponseReceivedEvent.TYPE, this);
        rootEventBus.addHandler(HttpRequestSendEvent.TYPE, this);
    }

    public void init(final long ID, final RequestBuilder requestBuilder) {
        this.requestBuilder = requestBuilder;
        UIBuilder.sessionID = ID;

        loadingMessageBox = new SimplePanel();

        communicationErrorMessagePanel = new PopupPanel(false, true);
        communicationErrorMessagePanel.setGlassEnabled(true);
        communicationErrorMessagePanel.setStyleName("pony-notification");
        communicationErrorMessagePanel.addStyleName("error");

        RootPanel.get().add(loadingMessageBox);

        loadingMessageBox.setStyleName("pony-LoadingMessageBox");
        loadingMessageBox.getElement().getStyle().setVisibility(Visibility.HIDDEN);
        loadingMessageBox.getElement().setInnerText("Loading ...");

        final PTCookies cookies = new PTCookies();
        cookies.create(null, null);
        objectByID.put(0l, cookies);

        // hide loading component
        final Widget w = RootPanel.get("loading");
        if (w == null) {
            Window.alert("Include splash screen html element into your index.html with id=\"loading\"");
        } else {
            w.setSize("0px", "0px");
            w.setVisible(false);
        }
    }

    @Override
    public void onCommunicationError(final Throwable exception) {

        rootEventBus.fireEvent(new CommunicationErrorEvent(exception));

        if (pendingClose) return;

        if (loadingMessageBox == null) {
            // First load failed
            if (exception instanceof StatusCodeException) {
                final StatusCodeException codeException = (StatusCodeException) exception;
                if (codeException.getStatusCode() == 0) return;
            }
            Window.alert("Cannot inititialize the application : " + exception.getMessage() + "\n" + exception + "\nPlease reload your application");
            return;
        }

        if (communicationErrorHandler != null) {
            if (exception instanceof StatusCodeException) {
                final StatusCodeException statusCodeException = (StatusCodeException) exception;
                communicationErrorHandler.onCommunicationError("" + statusCodeException.getStatusCode(), statusCodeException.getMessage());
            } else {
                communicationErrorHandler.onCommunicationError("x", exception.getMessage());
            }
        } else {
            if (exception instanceof StatusCodeException) {
                final StatusCodeException statusCodeException = (StatusCodeException) exception;
                showCommunicationErrorMessage(statusCodeException);
            } else {
                Window.alert("An unexcepted error occured: " + exception.getMessage() + ". Please check the server logs.");
            }
        }
    }

    @Override
    public void update(final JSONObject data) {

        final long receivedSeqNum = (long) data.get(APPLICATION.SEQ_NUM).isNumber().doubleValue();
        if ((lastReceived + 1) != receivedSeqNum) {
            incomingMessageQueue.put(receivedSeqNum, data);
            log.log(Level.SEVERE, "Wrong seqnum received. Expecting #" + (lastReceived + 1) + " but received #" + receivedSeqNum);
            return;
        }
        lastReceived = receivedSeqNum;

        final List<PTInstruction> instructions = new ArrayList<PTInstruction>();
        final JSONArray jsonArray = data.get(APPLICATION.INSTRUCTIONS).isArray();
        for (int i = 0; i < jsonArray.size(); i++) {
            instructions.add(new PTInstruction(jsonArray.get(i).isObject().getJavaScriptObject()));
        }

        if (!incomingMessageQueue.isEmpty()) {
            long expected = receivedSeqNum + 1;
            while (incomingMessageQueue.containsKey(expected)) {
                final JSONObject jsonObject = incomingMessageQueue.remove(expected);
                final JSONArray jsonArray2 = jsonObject.get(APPLICATION.INSTRUCTIONS).isArray();
                for (int i = 0; i < jsonArray2.size(); i++) {
                    instructions.add(new PTInstruction(jsonArray2.get(i).isObject().getJavaScriptObject()));
                }
                lastReceived = expected;
                expected++;
            }
            log.log(Level.SEVERE, "Message synchronized from #" + receivedSeqNum + " to #" + lastReceived);
        }

        updateMode = true;
        PTInstruction currentInstruction = null;
        try {

            for (final PTInstruction instruction : instructions) {
                currentInstruction = instruction;
                try {
                    processInstruction(instruction);
                } catch (final Throwable e) {
                    log.log(Level.SEVERE, "PonySDK has encountered an internal error on instruction : " + currentInstruction + " => Error Message " + e.getMessage() + ". ReceivedSeqNum: " + receivedSeqNum + " LastProcessSeqNum: "
                            + lastReceived, e);
                    stackError(currentInstruction, e);
                }
            }
        } catch (final Throwable e) {
            Window.alert("PonySDK has encountered an internal error on instruction : " + currentInstruction + " => Error Message " + e.getMessage() + ". ReceivedSeqNum: " + receivedSeqNum + " LastProcessSeqNum: " + lastReceived);
            log.log(Level.SEVERE, "PonySDK has encountered an internal error : ", e);
        } finally {
            flushEvents();
            updateMode = false;
        }
    }

    @Override
    public void stackError(final PTInstruction currentInstruction, final Throwable e) {
        String msg;
        if (e.getMessage() == null) msg = "NA";
        else msg = e.getMessage();

        final JSONObject jsoObject = new JSONObject();
        jsoObject.put("message", new JSONString("PonySDK has encountered an internal error on instruction : " + currentInstruction));
        jsoObject.put("details", new JSONString(msg));
        stackedErrors.add(jsoObject);
    }

    @Override
    public void processInstruction(final PTInstruction instruction) throws Exception {

        final String type = instruction.getString(TYPE.KEY);

        if (TYPE.KEY_.CLOSE.equals(type)) {
            pendingClose = true;
            sendDataToServer(instruction);

            final ScheduledCommand command = new ScheduledCommand() {

                @Override
                public void execute() {
                    PonySDK.reload();
                }
            };

            Scheduler.get().scheduleDeferred(command);
        } else if (TYPE.KEY_.CREATE.equals(type)) {
            PTObject ptObject;
            final boolean isAddon = instruction.containsKey("addOnSignature");
            if (isAddon) {
                final String addOnSignature = instruction.getString("addOnSignature");
                final AddonFactory addonFactory = addonByKey.get(addOnSignature);
                if (addonFactory == null) { throw new Exception("UIBuilder: AddOn factory not found for signature: " + addOnSignature + ", available: " + addonByKey.keySet()); }

                ptObject = addonFactory.newAddon();
                if (ptObject == null) { throw new Exception("UIBuilder: Failed to instanciate an Addon of type: " + addOnSignature); }
                ptObject.create(instruction, this);
            } else {
                ptObject = uiFactory.newUIObject(this, instruction);
                ptObject.create(instruction, this);
            }

            objectByID.put(instruction.getObjectID(), ptObject);

        } else if (TYPE.KEY_.ADD.equals(type)) {
            final PTObject uiObject = objectByID.get(instruction.getParentID());
            if (uiObject == null) {
                log.info("Cannot add object to an garbaged parent object #" + instruction.getObjectID());
                return;
            }
            uiObject.add(instruction, this);

        } else if (TYPE.KEY_.ADD_HANDLER.equals(type)) {
            final String handler = instruction.getString(HANDLER.KEY);
            if (HANDLER.KEY_.STREAM_REQUEST_HANDLER.equals(handler)) {
                new PTStreamResource().addHandler(instruction, this);
            } else {
                final PTObject uiObject = objectByID.get(instruction.getObjectID());
                uiObject.addHandler(instruction, this);
            }
        } else if (TYPE.KEY_.REMOVE_HANDLER.equals(type)) {
            final PTObject uiObject = objectByID.get(instruction.getObjectID());
            uiObject.removeHandler(instruction, this);

        } else if (TYPE.KEY_.REMOVE.equals(type)) {
            PTObject ptObject;
            if (instruction.getParentID() == -1) ptObject = objectByID.get(instruction.getObjectID());
            else {
                ptObject = objectByID.get(instruction.getParentID());
            }
            if (ptObject == null) {
                log.info("Cannot remove an garbaged object #" + instruction.getObjectID());
                return;
            }
            ptObject.remove(instruction, this);
        } else if (TYPE.KEY_.GC.equals(type)) {
            final PTObject unRegisterObject = unRegisterObject(instruction.getObjectID());
            if (unRegisterObject == null) {
                log.info("Cannot GC an garbaged object #" + instruction.getObjectID());
                return;
            }
            unRegisterObject.gc(this);
        } else if (TYPE.KEY_.UPDATE.equals(type)) {
            final PTObject ptObject = objectByID.get(instruction.getObjectID());
            if (ptObject == null) {
                log.info("Cannot update an garbaged object #" + instruction.getObjectID());
                return;
            }
            ptObject.update(instruction, this);
        } else if (TYPE.KEY_.HISTORY.equals(type)) {
            final String oldToken = History.getToken();

            String token = null;

            if (instruction.containsKey(HISTORY.TOKEN)) {
                token = instruction.getString(HISTORY.TOKEN);
            }

            if (oldToken != null && oldToken.equals(token)) {
                if (instruction.getBoolean(HISTORY.FIRE_EVENTS)) History.fireCurrentHistoryState();
            } else {
                History.newItem(token, instruction.getBoolean(HISTORY.FIRE_EVENTS));
            }
        }

    }

    protected void updateIncomingSeqNum(final long receivedSeqNum) {
        final long previous = lastReceived;
        if ((previous + 1) != receivedSeqNum) {
            log.log(Level.SEVERE, "Wrong seqnum received. Expecting #" + (previous + 1) + " but received #" + receivedSeqNum);
        }
        lastReceived = receivedSeqNum;
    }

    @Override
    public PTObject unRegisterObject(final Long objectId) {
        final PTObject ptObject = objectByID.remove(objectId);
        final UIObject uiObject = widgetIDByObjectID.remove(objectId);
        if (uiObject != null) {
            objectIDByWidget.remove(uiObject);
        }
        return ptObject;
    }

    @Override
    public void stackInstrution(final PTInstruction instruction) {
        if (!updateMode) sendDataToServer(instruction);
        else stackedInstructions.add(instruction);
    }

    @Override
    public void flushEvents() {
        if (stackedInstructions.isEmpty()) return;
        sendDataToServer(stackedInstructions);
        stackedInstructions.clear();
    }

    private void sendDataToServer(final List<PTInstruction> instructions) {

        final PTInstruction requestData = new PTInstruction();
        requestData.put(APPLICATION.VIEW_ID, sessionID);

        final JSONArray jsonArray = new JSONArray();
        for (int i = 0; i < instructions.size(); i++) {
            jsonArray.set(i, instructions.get(i));
        }

        final JSONArray errors = new JSONArray();
        if (!stackedErrors.isEmpty()) {
            int i = 0;
            for (final JSONObject jsoObject : stackedErrors) {
                errors.set(i++, jsoObject);
            }
            stackedErrors.clear();
        }

        requestData.put(APPLICATION.INSTRUCTIONS, jsonArray);
        requestData.put(APPLICATION.ERRORS, errors);
        requestData.put(APPLICATION.SEQ_NUM, nextSent++);

        requestBuilder.send(requestData.toString());
    }

    @Override
    public void sendDataToServer(final PTInstruction instruction) {
        final List<PTInstruction> instructions = new ArrayList<PTInstruction>();
        instructions.add(instruction);
        sendDataToServer(instructions);
    }

    private Timer scheduleLoadingMessageBox() {

        if (loadingMessageBox == null) return null;

        final Timer timer = new Timer() {

            @Override
            public void run() {
                loadingMessageBox.getElement().getStyle().setVisibility(Visibility.VISIBLE);
            }
        };
        timer.schedule(500);
        return timer;
    }

    private void showCommunicationErrorMessage(final StatusCodeException caught) {

        final VerticalPanel content = new VerticalPanel();
        final HorizontalPanel actionPanel = new HorizontalPanel();
        actionPanel.setSize("100%", "100%");

        if (caught.getStatusCode() == ServerException.INVALID_SESSION) {
            content.add(new HTML("Server connection failed <br/>Code : " + caught.getStatusCode() + "<br/>" + "Cause : " + caught.getMessage()));

            final Anchor reloadAnchor = new Anchor("reload");
            reloadAnchor.addClickHandler(new ClickHandler() {

                @Override
                public void onClick(final ClickEvent event) {
                    History.newItem("");
                    PonySDK.reload();
                }
            });

            actionPanel.add(reloadAnchor);
            actionPanel.setCellHorizontalAlignment(reloadAnchor, HasHorizontalAlignment.ALIGN_CENTER);
            actionPanel.setCellVerticalAlignment(reloadAnchor, HasVerticalAlignment.ALIGN_MIDDLE);
        } else {
            content.add(new HTML("An unexpected error occured <br/>Code : " + caught.getStatusCode() + "<br/>" + "Cause : " + caught.getMessage()));
        }

        final Anchor closeAnchor = new Anchor("close");
        closeAnchor.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(final ClickEvent event) {
                communicationErrorMessagePanel.hide();
            }
        });
        actionPanel.add(closeAnchor);
        actionPanel.setCellHorizontalAlignment(closeAnchor, HasHorizontalAlignment.ALIGN_CENTER);
        actionPanel.setCellVerticalAlignment(closeAnchor, HasVerticalAlignment.ALIGN_MIDDLE);

        content.add(actionPanel);

        communicationErrorMessagePanel.setWidget(content);
        communicationErrorMessagePanel.setPopupPositionAndShow(new PositionCallback() {

            @Override
            public void setPosition(final int offsetWidth, final int offsetHeight) {
                communicationErrorMessagePanel.setPopupPosition((Window.getClientWidth() - offsetWidth) / 2, (Window.getClientHeight() - offsetHeight) / 2);
            }
        });
    }

    @Override
    public void onValueChange(final ValueChangeEvent<String> event) {
        if (event.getValue() != null && !event.getValue().isEmpty()) {
            final PTInstruction eventInstruction = new PTInstruction();
            eventInstruction.put(TYPE.KEY, TYPE.KEY_.HISTORY);
            eventInstruction.put(HISTORY.TOKEN, event.getValue());
            stackInstrution(eventInstruction);
        }
    }

    @Override
    public PTObject getPTObject(final Long ID) {
        return objectByID.get(ID);
    }

    @Override
    public PTObject getPTObject(final UIObject uiObject) {
        final Long objectID = objectIDByWidget.get(uiObject);
        if (objectID != null) return objectByID.get(objectID);
        return null;
    }

    @Override
    public void registerUIObject(final Long ID, final UIObject uiObject) {
        objectIDByWidget.put(uiObject, ID);
        widgetIDByObjectID.put(ID, uiObject);
    }

    @Override
    public void onHttpRequestSend(final HttpRequestSendEvent event) {
        numberOfrequestInProgress++;

        if (timer == null) timer = scheduleLoadingMessageBox();
    }

    @Override
    public void onHttpResponseReceivedEvent(final HttpResponseReceivedEvent event) {
        if (numberOfrequestInProgress > 0) {
            numberOfrequestInProgress--;
        }

        hideLoadingMessageBox();
    }

    private void hideLoadingMessageBox() {

        if (loadingMessageBox == null) return;

        if (numberOfrequestInProgress < 1 && timer != null) {
            timer.cancel();
            timer = null;
            loadingMessageBox.getElement().getStyle().setVisibility(Visibility.HIDDEN);
        }
    }

    public void executeInstruction(final JavaScriptObject jso) {
        update(new JSONObject(jso));
    }

    public static EventBus getRootEventBus() {
        return rootEventBus;
    }

    public void registerCommunicationError(final CommunicationErrorHandler communicationErrorClosure) {
        this.communicationErrorHandler = communicationErrorClosure;
    }

    public void registerJavascriptAddOnFactory(final String signature, final JavascriptAddOnFactory javascriptAddOnFactory) {
        this.javascriptAddOnFactories.put(signature, javascriptAddOnFactory);
    }

    @Override
    public Map<String, JavascriptAddOnFactory> getJavascriptAddOnFactory() {
        return javascriptAddOnFactories;
    }

}
TOP

Related Classes of com.ponysdk.ui.terminal.UIBuilder

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.