    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.corelib.mixins;
    014
    015import org.apache.tapestry5.ComponentResources;
    016import org.apache.tapestry5.MarkupWriter;
    017import org.apache.tapestry5.MarkupWriterListener;
    018import org.apache.tapestry5.TapestryConstants;
    019import org.apache.tapestry5.annotations.PublishEvent;
    020import org.apache.tapestry5.dom.Element;
    021import org.apache.tapestry5.internal.InternalConstants;
    022import org.apache.tapestry5.ioc.annotations.Inject;
    023import org.apache.tapestry5.json.JSONArray;
    024import org.apache.tapestry5.json.JSONObject;
    025import org.apache.tapestry5.model.ComponentModel;
    026
    027/**
    028 * Tapestry internal mixin used to implement the {@link PublishEvent} event logic. Don't use directly.
    029 */
    030public class PublishServerSideEvents
    031{
    032
    033    private static final String PUBLISH_COMPONENT_EVENTS_URL_PROPERTY = InternalConstants.PUBLISH_COMPONENT_EVENTS_URL_PROPERTY;
    034    private static final String COMPONENT_EVENTS_ATTRIBUTE_NAME = TapestryConstants.COMPONENT_EVENTS_ATTRIBUTE_NAME;
    035
    036    @Inject
    037    private ComponentResources resources;
    038
    039    void beginRender(final MarkupWriter writer) {
    040
    041        final Element element = writer.getElement();
    042
    043        // When the component is actually a page, nothing was rendered yet.
    044        // The listener we add here will add the events attribute to the <body> element
    045        // later
    046        if (element == null) {
    047            writer.addListener(new BodyElementListener(writer));
    048        }
    049        else {
    050            writer.addListener(new DelayedListener(writer));
    051        }
    052
    053    }
    054
    055    private void addEventsAttribute(final Element element)
    056    {
    057
    058        if (element == null)
    059        {
    060            throw new IllegalStateException("@PublishEvent used inside a page which didn't generate a <body> element");
    061        }
    062
    063        final ComponentResources containerResources = resources.getContainerResources();
    064        final ComponentModel componentModel = containerResources.getComponentModel();
    065        final String metaValue = componentModel.getMeta(InternalConstants.PUBLISH_COMPONENT_EVENTS_META);
    066        final JSONArray componentEvents = new JSONArray(metaValue);
    067        final JSONObject events = new JSONObject();
    068        final String existingValue = element.getAttribute(COMPONENT_EVENTS_ATTRIBUTE_NAME);
    069
    070        if (existingValue != null)
    071        {
    072            final JSONObject existing = new JSONObject(existingValue);
    073            for (String key : existing.keys()) {
    074                events.put(key, existing.get(key));
    075            }
    076        }
    077
    078        for (int i = 0; i < componentEvents.length(); i++)
    079        {
    080            final String eventName = componentEvents.getString(i);
    081            JSONObject event = new JSONObject();
    082            event.put(PUBLISH_COMPONENT_EVENTS_URL_PROPERTY, containerResources.createEventLink(eventName).toString());
    083            events.put(eventName, event);
    084        }
    085
    086        element.forceAttributes(TapestryConstants.COMPONENT_EVENTS_ATTRIBUTE_NAME, events.toString());
    087    }
    088
    089    final private class DelayedListener implements MarkupWriterListener {
    090
    091        private MarkupWriter writer;
    092
    093        private Element element;
    094
    095        public DelayedListener(MarkupWriter writer)
    096        {
    097            super();
    098            this.writer = writer;
    099        }
    100
    101        @Override
    102        public void elementDidStart(Element element)
    103        {
    104            // Store first element generated by rendering the component
    105            if (this.element == null)
    106            {
    107                this.element = element;
    108            }
    109        }
    110
    111        @Override
    112        public void elementDidEnd(Element element)
    113        {
    114            if (this.element == null)
    115            {
    116                throw new IllegalStateException("@PublishEvent used inside a component which didn't generate any HTML elements");
    117            }
    118            addEventsAttribute(this.element);
    119            writer.removeListener(this);
    120        }
    121
    122    }
    123
    124    final private class BodyElementListener implements MarkupWriterListener {
    125
    126        private MarkupWriter writer;
    127
    128        public BodyElementListener(MarkupWriter writer)
    129        {
    130            super();
    131            this.writer = writer;
    132        }
    133
    134        @Override
    135        public void elementDidStart(Element element)
    136        {
    137            if (element.getName().equals("body"))
    138            {
    139                addEventsAttribute(element);
    140                writer.removeListener(this);
    141            }
    142        }
    143
    144        @Override
    145        public void elementDidEnd(Element element)
    146        {
    147        }
    148
    149    }
    150
    151}