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 }