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}