001    // Copyright 2007, 2008, 2010 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.ContentType;
018    import org.apache.tapestry5.TrackableComponentEventCallback;
019    import org.apache.tapestry5.internal.InternalConstants;
020    import org.apache.tapestry5.internal.structure.ComponentPageElement;
021    import org.apache.tapestry5.internal.structure.Page;
022    import org.apache.tapestry5.internal.util.Holder;
023    import org.apache.tapestry5.ioc.internal.util.TapestryException;
024    import org.apache.tapestry5.json.JSONObject;
025    import org.apache.tapestry5.services.*;
026    
027    import java.io.IOException;
028    
029    /**
030     * Similar to {@link ComponentEventRequestHandlerImpl}, but built around the Ajax request cycle, where the action
031     * request sends back an immediate JSON response containing the new content.
032     */
033    @SuppressWarnings("unchecked")
034    public class AjaxComponentEventRequestHandler implements ComponentEventRequestHandler
035    {
036        private final RequestPageCache cache;
037    
038        private final Request request;
039    
040        private final PageRenderQueue queue;
041    
042        private final ComponentEventResultProcessor resultProcessor;
043    
044        private final PageContentTypeAnalyzer pageContentTypeAnalyzer;
045    
046        private final Environment environment;
047    
048        private final AjaxPartialResponseRenderer partialRenderer;
049    
050        private final PageActivator pageActivator;
051    
052        public AjaxComponentEventRequestHandler(RequestPageCache cache, Request request, PageRenderQueue queue, @Ajax
053        ComponentEventResultProcessor resultProcessor, PageActivator pageActivator,
054                                                PageContentTypeAnalyzer pageContentTypeAnalyzer, Environment environment,
055                                                AjaxPartialResponseRenderer partialRenderer)
056        {
057            this.cache = cache;
058            this.queue = queue;
059            this.resultProcessor = resultProcessor;
060            this.pageActivator = pageActivator;
061            this.pageContentTypeAnalyzer = pageContentTypeAnalyzer;
062            this.request = request;
063            this.environment = environment;
064            this.partialRenderer = partialRenderer;
065        }
066    
067        public void handle(ComponentEventRequestParameters parameters) throws IOException
068        {
069            Page activePage = cache.get(parameters.getActivePageName());
070    
071            final Holder<Boolean> resultProcessorInvoked = Holder.create();
072            resultProcessorInvoked.put(false);
073    
074            ComponentEventResultProcessor interceptor = new ComponentEventResultProcessor()
075            {
076                public void processResultValue(Object value) throws IOException
077                {
078                    resultProcessorInvoked.put(true);
079    
080                    resultProcessor.processResultValue(value);
081                }
082            };
083    
084            // If we end up doing a partial render, the page render queue service needs to know the
085            // page that will be rendered (for logging purposes, if nothing else).
086    
087            queue.setRenderingPage(activePage);
088    
089            if (pageActivator.activatePage(activePage.getRootElement().getComponentResources(), parameters
090                    .getPageActivationContext(), interceptor))
091                return;
092    
093            ContentType contentType = pageContentTypeAnalyzer.findContentType(activePage);
094    
095            request.setAttribute(InternalConstants.CONTENT_TYPE_ATTRIBUTE_NAME, contentType);
096    
097            Page containerPage = cache.get(parameters.getContainingPageName());
098    
099            ComponentPageElement element = containerPage.getComponentElementByNestedId(parameters.getNestedComponentId());
100    
101            // In many cases, the triggered element is a Form that needs to be able to
102            // pass its event handler return values to the correct result processor.
103            // This is certainly the case for forms.
104    
105            TrackableComponentEventCallback callback = new ComponentResultProcessorWrapper(interceptor);
106    
107            environment.push(ComponentEventResultProcessor.class, interceptor);
108            environment.push(TrackableComponentEventCallback.class, callback);
109    
110            boolean handled = element
111                    .triggerContextEvent(parameters.getEventType(), parameters.getEventContext(), callback);
112    
113            if (!handled)
114                throw new TapestryException(ServicesMessages.eventNotHandled(element, parameters.getEventType()), element,
115                        null);
116    
117            environment.pop(TrackableComponentEventCallback.class);
118            environment.pop(ComponentEventResultProcessor.class);
119    
120    
121            // If the result processor was passed a value, then it will already have rendered. Otherwise it was not passed a value,
122            // but it's still possible that we still want to do a partial page render ... if filters were added to the render queue.
123            // In that event, run the partial page render now and return.
124    
125            boolean wasInvoked = resultProcessorInvoked.get();
126    
127            if ((!wasInvoked) && queue.isPartialRenderInitialized())
128            {
129                partialRenderer.renderPartialPageMarkup();
130                return;
131            }
132    
133            // If the result processor was passed a value, then it will already have rendered, and there is nothing more to do.
134    
135            if (wasInvoked) { return; }
136    
137            // Send an empty JSON reply if no value was returned from the component event handler method.
138            // This is the typical behavior when an Ajax component event handler returns null.
139    
140            JSONObject reply = new JSONObject();
141    
142            resultProcessor.processResultValue(reply);
143        }
144    }