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

com.webfirmframework.wffweb.server.page.BrowserPage Maven / Gradle / Ivy

There is a newer version: 12.0.1
Show newest version
/*
 * Copyright 2014-2016 Web Firm Framework
 *
 * 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.webfirmframework.wffweb.server.page;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.server.page.js.WffJsFile;
import com.webfirmframework.wffweb.tag.html.AbstractHtml;
import com.webfirmframework.wffweb.tag.html.TagNameConstants;
import com.webfirmframework.wffweb.tag.html.attribute.Type;
import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;
import com.webfirmframework.wffweb.tag.html.attribute.event.EventAttribute;
import com.webfirmframework.wffweb.tag.html.attribute.event.ServerAsyncMethod;
import com.webfirmframework.wffweb.tag.html.attribute.listener.AttributeValueChangeListener;
import com.webfirmframework.wffweb.tag.html.html5.attribute.global.DataWffId;
import com.webfirmframework.wffweb.tag.html.programming.Script;
import com.webfirmframework.wffweb.tag.htmlwff.NoTag;
import com.webfirmframework.wffweb.util.WffBinaryMessageUtil;
import com.webfirmframework.wffweb.util.data.NameValue;
import com.webfirmframework.wffweb.wffbm.data.WffBMObject;

/**
 * @author WFF
 * @since 2.0.0
 */
public abstract class BrowserPage implements Serializable {

    // if this class' is refactored then SecurityClassConstants should be
    // updated.

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(BrowserPage.class.getName());

    private static final boolean PRODUCTION_MODE = true;

    private String instanceId;

    private AttributeValueChangeListener valueChangeListener;

    private Map tagByWffId;

    private AbstractHtml abstractHtml;

    private WebSocketPushListener wsListener;

    private DataWffId wffScriptTagId;

    private static final Security ACCESS_OBJECT;

    // for security purpose, the class name should not be modified
    private static final class Security implements Serializable {

        private static final long serialVersionUID = 1L;

        private Security() {
        }
    }

    static {
        ACCESS_OBJECT = new Security();
    }

    {
        instanceId = UUID.randomUUID().toString();
    }

    public abstract String webSocketUrl();

    /**
     * @param wsListener
     * @since 2.0.0
     * @author WFF
     */
    public void setWebSocketPushListener(
            final WebSocketPushListener wsListener) {
        this.wsListener = wsListener;
    }

    public WebSocketPushListener getWsListener() {
        return wsListener;
    }

    DataWffId getNewDataWffId() {
        return abstractHtml.getSharedObject().getNewDataWffId(ACCESS_OBJECT);
    }

    /**
     * @param message
     *            the bytes the received in onmessage
     * @since 2.0.0
     * @author WFF
     */
    public void websocketMessaged(final byte[] message) {
        try {
            executeWffBMTask(message);
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    public abstract AbstractHtml render();

    /**
     * executes the task in the given wff binary message
     *
     * @since 2.0.0
     * @author WFF
     * @throws UnsupportedEncodingException
     */
    private void executeWffBMTask(final byte[] message)
            throws UnsupportedEncodingException {

        final List nameValues = WffBinaryMessageUtil.VERSION_1
                .parse(message);

        final NameValue task = nameValues.get(0);
        final byte taskValue = task.getValues()[0][0];

        if (Task.TASK.getValueByte() == task.getName()[0]) {

            // IM stands for Invoke Method
            if (taskValue == Task.INVOKE_ASYNC_METHOD.getValueByte()) {

                //@formatter:off
                // invoke method task format :-
                // { "name": task_byte, "values" : [invoke_method_byte_from_Task_enum]}, { "name": data-wff-id, "values" : [ event_attribute_name ]}
                // { "name": 2, "values" : [[0]]}, { "name":"C55", "values" : ["onclick"]}
                //@formatter:on

                final NameValue wffTagIdAndAttrName = nameValues.get(1);
                final byte[] intBytes = new byte[wffTagIdAndAttrName
                        .getName().length - 1];
                System.arraycopy(wffTagIdAndAttrName.getName(), 1, intBytes, 0,
                        intBytes.length);

                final String wffTagId = new String(
                        wffTagIdAndAttrName.getName(), 0, 1, "UTF-8")
                        + WffBinaryMessageUtil
                                .getIntFromOptimizedBytes(intBytes);

                final byte[][] values = wffTagIdAndAttrName.getValues();
                final String eventAttrName = new String(values[0], "UTF-8");

                WffBMObject wffBMObject = null;
                if (values.length > 1) {
                    final byte[] wffBMObjectBytes = values[1];
                    wffBMObject = new WffBMObject(wffBMObjectBytes, true);

                }

                final AbstractHtml methodTag = tagByWffId.get(wffTagId);
                if (methodTag != null) {

                    final AbstractAttribute dataWffIdAttr = methodTag
                            .getAttributeByName(eventAttrName);

                    if (dataWffIdAttr != null) {

                        if (dataWffIdAttr instanceof EventAttribute) {

                            final EventAttribute eventAttr = (EventAttribute) dataWffIdAttr;

                            final ServerAsyncMethod serverAsyncMethod = eventAttr
                                    .getServerAsyncMethod();

                            final ServerAsyncMethod.Event event = new ServerAsyncMethod.Event(
                                    methodTag);

                            final WffBMObject returnedObject = serverAsyncMethod
                                    .asyncMethod(wffBMObject, event);

                            final String jsPostFunctionBody = eventAttr
                                    .getJsPostFunctionBody();

                            if (jsPostFunctionBody != null) {

                                final NameValue invokePostFunTask = Task.INVOKE_POST_FUNCTION
                                        .getTaskNameValue();
                                final NameValue nameValue = new NameValue();
                                // name as function body string and value at
                                // zeroth index as
                                // wffBMObject bytes
                                nameValue.setName(
                                        jsPostFunctionBody.getBytes("UTF-8"));

                                if (returnedObject != null) {
                                    nameValue.setValues(new byte[][] {
                                            returnedObject.build(true) });
                                }

                                wsListener.push(WffBinaryMessageUtil.VERSION_1
                                        .getWffBinaryMessageBytes(
                                                invokePostFunTask, nameValue));
                            }

                        } else {
                            LOGGER.severe(dataWffIdAttr
                                    + " is NOT instanceof EventAttribute");
                        }

                    } else {
                        LOGGER.severe("no event attribute found for "
                                + dataWffIdAttr);
                    }

                } else {
                    if (!PRODUCTION_MODE) {
                        LOGGER.severe("No tag found for wffTagId " + wffTagId);
                    }
                }

            }

        }

    }

    private void addAttrValueChangeListener(final AbstractHtml abstractHtml) {

        if (valueChangeListener == null) {
            valueChangeListener = new AttributeValueChangeListenerImpl(
                    BrowserPage.this, tagByWffId);
        }

        abstractHtml.getSharedObject()
                .setValueChangeListener(valueChangeListener, ACCESS_OBJECT);
    }

    private void addDataWffIdAttribute(final AbstractHtml abstractHtml) {

        final Deque> childrenStack = new ArrayDeque>();
        childrenStack.push(
                new LinkedHashSet(Arrays.asList(abstractHtml)));

        while (childrenStack.size() > 0) {

            final Set children = childrenStack.pop();

            for (final AbstractHtml child : children) {

                if (child.getDataWffId() == null) {
                    child.setDataWffId(getNewDataWffId());
                }

                tagByWffId.put(child.getDataWffId().getValue(), child);

                final Set subChildren = child
                        .getChildren(ACCESS_OBJECT);

                if (subChildren != null && subChildren.size() > 0) {
                    childrenStack.push(subChildren);
                }

            }

        }

    }

    private void embedWffScriptIfRequired(final AbstractHtml abstractHtml,
            final String wsUrlWithInstanceId) {

        if (wffScriptTagId != null && tagByWffId.containsKey(wffScriptTagId)) {
            // no need to add script tag if it exists in the ui
            return;
        }

        final Deque> childrenStack = new ArrayDeque>();
        childrenStack.push(
                new LinkedHashSet(Arrays.asList(abstractHtml)));

        boolean bodyTagMissing = true;

        outerLoop: while (childrenStack.size() > 0) {

            final Set children = childrenStack.pop();

            for (final AbstractHtml child : children) {

                if (TagNameConstants.BODY.equals(child.getTagName())) {

                    bodyTagMissing = false;

                    wffScriptTagId = getNewDataWffId();

                    final Script script = new Script(null,
                            new Type("text/javascript"));

                    script.setDataWffId(wffScriptTagId);

                    new NoTag(script, WffJsFile
                            .getAllOptimizedContent(wsUrlWithInstanceId));

                    // to avoid invoking listener
                    child.addChild(ACCESS_OBJECT, script, false);

                    // ConcurrentHashMap cannot contain null as value
                    tagByWffId.put(wffScriptTagId.getValue(), script);

                    break outerLoop;
                }

                final Set subChildren = child
                        .getChildren(ACCESS_OBJECT);

                if (subChildren != null && subChildren.size() > 0) {
                    childrenStack.push(subChildren);
                }
            }

        }

        if (bodyTagMissing) {
            wffScriptTagId = getNewDataWffId();

            final Script script = new Script(null, new Type("text/javascript"));

            script.setDataWffId(wffScriptTagId);

            new NoTag(script,
                    WffJsFile.getAllOptimizedContent(wsUrlWithInstanceId));

            // to avoid invoking listener
            abstractHtml.addChild(ACCESS_OBJECT, script, false);

            // ConcurrentHashMap cannot contain null as value
            tagByWffId.put(wffScriptTagId.getValue(), script);

        }

    }

    private void addChildTagAppendListener(final AbstractHtml abstractHtml) {

        abstractHtml.getSharedObject().setChildTagAppendListener(
                new ChildTagAppendListenerImpl(this, ACCESS_OBJECT, tagByWffId),
                ACCESS_OBJECT);
    }

    private void addChildTagRemoveListener(final AbstractHtml abstractHtml) {

        abstractHtml.getSharedObject().setChildTagRemoveListener(
                new ChildTagRemoveListenerImpl(this, ACCESS_OBJECT, tagByWffId),
                ACCESS_OBJECT);
    }

    private void addAttributeAddListener(final AbstractHtml abstractHtml) {
        abstractHtml.getSharedObject().setAttributeAddListener(
                new AttributeAddListenerImpl(this), ACCESS_OBJECT);
    }

    private void addAttributeRemoveListener(final AbstractHtml abstractHtml) {
        abstractHtml.getSharedObject().setAttributeRemoveListener(
                new AttributeRemoveListenerImpl(this, tagByWffId),
                ACCESS_OBJECT);
    }

    private void addInnerHtmlAddListener(final AbstractHtml abstractHtml) {
        abstractHtml.getSharedObject().setInnerHtmlAddListener(
                new InnerHtmlAddListenerImpl(this, ACCESS_OBJECT, tagByWffId),
                ACCESS_OBJECT);
    }

    /**
     * @return {@code String} equalent to the html string of the tag including
     *         the child tags.
     *
     * @author WFF
     */
    public final String toHtmlString() {
        initAbstractHtml();
        return abstractHtml.toHtmlString(true);
    }

    /**
     * @param charset
     *            the charset
     * @return {@code String} equalent to the html string of the tag including
     *         the child tags.
     * @author WFF
     */
    public final String toHtmlString(final String charset) {
        initAbstractHtml();
        return abstractHtml.toHtmlString(true, charset);
    }

    /**
     * @param os
     *            the object of {@code OutputStream} to write to.
     *
     * @throws IOException
     */
    public final void toOutputStream(final OutputStream os) throws IOException {
        initAbstractHtml();
        abstractHtml.toOutputStream(os, true);
    }

    /**
     * @param os
     *            the object of {@code OutputStream} to write to.
     * @param rebuild
     *            true to rebuild & false to write previously built bytes.
     * @param charset
     *            the charset
     * @throws IOException
     */
    public final void toOutputStream(final OutputStream os,
            final String charset) throws IOException {
        initAbstractHtml();

        abstractHtml.toOutputStream(os, true, charset);
    }

    private void initAbstractHtml() {
        if (abstractHtml == null) {
            abstractHtml = render();

            if (abstractHtml == null) {
                throw new NullValueException(
                        "render must return an instance of AbstractHtml, eg:- new Html(null);");
            }

            tagByWffId = abstractHtml.getSharedObject()
                    .initTagByWffId(ACCESS_OBJECT);

            addDataWffIdAttribute(abstractHtml);
            // attribute value change listener
            // should be added only after adding data-wff-id attribute
            addAttrValueChangeListener(abstractHtml);
            addChildTagAppendListener(abstractHtml);
            addChildTagRemoveListener(abstractHtml);
            addAttributeAddListener(abstractHtml);
            addAttributeRemoveListener(abstractHtml);
            addInnerHtmlAddListener(abstractHtml);
        }

        final String webSocketUrl = webSocketUrl();
        if (webSocketUrl == null) {
            throw new NullValueException(
                    "webSocketUrl must return valid websocket url");
        }

        final String wsUrlWithInstanceId = webSocketUrl.indexOf("?") == -1
                ? webSocketUrl + "?wffInstanceId=" + getInstanceId()
                : webSocketUrl + "&wffInstanceId=" + getInstanceId();

        embedWffScriptIfRequired(abstractHtml, wsUrlWithInstanceId);
    }

    /**
     * @return a unique id for this instance
     * @since 2.0.0
     * @author WFF
     */
    public String getInstanceId() {
        return instanceId;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy