View Javadoc

1   /*
2    * Copyright 2004 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.nordija.tapestry.bayeux.stream;
17  
18  import org.apache.commons.logging.Log;
19  import org.apache.commons.logging.LogFactory;
20  import org.apache.hivemind.ApplicationRuntimeException;
21  import org.apache.hivemind.util.Defense;
22  import org.apache.tapestry.IComponent;
23  import org.apache.tapestry.IPage;
24  import org.apache.tapestry.IRequestCycle;
25  import org.apache.tapestry.StaleSessionException;
26  import org.apache.tapestry.engine.EngineMessages;
27  import org.apache.tapestry.engine.IEngineService;
28  import org.apache.tapestry.engine.ILink;
29  import org.apache.tapestry.services.LinkFactory;
30  import org.apache.tapestry.services.ServiceConstants;
31  import org.apache.tapestry.spec.IApplicationSpecification;
32  import org.apache.tapestry.web.WebRequest;
33  import org.apache.tapestry.web.WebSession;
34  
35  import javax.servlet.http.HttpServletResponse;
36  import java.io.IOException;
37  import java.io.OutputStream;
38  import java.util.HashMap;
39  import java.util.Map;
40  
41  /**
42   * A service which can render links to stream resources and accept incoming requests
43   * from such links, looking up the stream resource and dispatching to the resource.
44   * <p/>
45   * This service depends on the definition of a tapestry-extension which must point to a
46   * implementation of {@link StreamResourceSource}. Please see docs for further info.
47   *
48   * @author Per Olesen www.nordija.com
49   * @version $Id: StreamService.java 124 2007-06-02 11:36:55Z jeyben $
50   */
51  public class StreamService implements IEngineService {
52  
53      public static final String SERVICE_NAME = "stream";
54  
55      public static final String RESOURCE_ID_PARAM_NAME = "resourceId";
56      public static final String STREAM_RESOURCE_SOURCE_EXT_PARAM_NAME = "streamResourceSourceExtentionName";
57      public static final String BAYEUX_STREAM_EXTENTIONS_PREFIX = "bayeux.stream.";
58      public static final String DEFAULT_RESOURCE_SOURCE_NAME = "resourcesource";
59  
60      private static final Log LOG = LogFactory.getLog(StreamService.class);
61  
62      private HttpServletResponse response;
63  
64      private LinkFactory linkFactory;
65  
66      private IRequestCycle requestCycle;
67  
68      private WebRequest webRequest;
69  
70      public void setResponse(HttpServletResponse response) {
71          this.response = response;
72      }
73  
74      public void setLinkFactory(LinkFactory linkFactory) {
75          this.linkFactory = linkFactory;
76      }
77  
78      public String getName() {
79          return SERVICE_NAME;
80      }
81  
82      public ILink getLink(boolean post, Object parameter) {
83          Defense.isAssignable(parameter, StreamServiceParameter.class, "parameter");
84  
85          StreamServiceParameter ssp = (StreamServiceParameter) parameter;
86  
87          IComponent component = ssp.getStream();
88  
89          // New since 1.0.1, we use the component to determine
90          // the page, not the cycle. Through the use of tricky
91          // things such as Block/InsertBlock, it is possible
92          // that a component from a page different than
93          // the response page will render.
94          // In 1.0.6, we start to record *both* the render page
95          // and the component page (if different).
96  
97          IPage activePage = requestCycle.getPage();
98          IPage componentPage = component.getPage();
99  
100         Map parameters = new HashMap();
101 
102         boolean stateful = webRequest.getSession(false) != null;
103 
104         parameters.put(ServiceConstants.PAGE, activePage.getPageName());
105         parameters.put(ServiceConstants.COMPONENT, component.getIdPath());
106         parameters.put(ServiceConstants.CONTAINER, componentPage == activePage ? null
107                 : componentPage.getPageName());
108         parameters.put(ServiceConstants.SESSION, stateful ? "T" : null);
109 
110         //put resourceSourceName into parameter list
111         parameters.put(STREAM_RESOURCE_SOURCE_EXT_PARAM_NAME, ssp.getStreamResourceSourceExtentionName());
112 
113         // handle dynamic XHR/JSON parameters
114         if (ssp.isAsync() || ssp.isJson()) {
115             if (ssp.getUpdateParts() != null && ssp.getUpdateParts().length > 0)
116                 parameters.put(ServiceConstants.UPDATE_PARTS, ssp.getUpdateParts());
117 
118             if (ssp.isJson())
119                 parameters.put("json", String.valueOf(ssp.isJson()));
120 
121             // don't need to put isAsync parameter in as that is handled via http headers
122         }
123 
124         parameters.put(ServiceConstants.PARAMETER, ssp.getServiceParameters());
125 
126         return linkFactory.constructLink(this, post, parameters, true);
127     }
128 
129     public void service(IRequestCycle cycle) throws IOException {
130         String componentId = cycle.getParameter(ServiceConstants.COMPONENT);
131         String componentPageName = cycle.getParameter(ServiceConstants.CONTAINER);
132         String activePageName = cycle.getParameter(ServiceConstants.PAGE);
133         boolean activeSession = cycle.getParameter(ServiceConstants.SESSION) != null;
134 
135         //fetch the resource source extention name
136         String streamResourceSourceExtName = cycle.getParameter(STREAM_RESOURCE_SOURCE_EXT_PARAM_NAME);
137 
138         IPage page = cycle.getPage(activePageName);
139 
140         cycle.activate(page);
141 
142         IPage componentPage = componentPageName == null ? page : cycle.getPage(componentPageName);
143 
144         IComponent component = componentPage.getNestedComponent(componentId);
145 
146         IStream stream;
147 
148         try {
149             stream = (IStream) component;
150         }
151         catch (ClassCastException ex) {
152             throw new ApplicationRuntimeException(EngineMessages.wrongComponentType(
153                     component,
154                     IStream.class), component, null, ex);
155         }
156 
157         // Check for a StaleSession only when the session was stateful when
158         // the link was created.
159 
160         if (activeSession && stream.isStateful()) {
161             WebSession session = webRequest.getSession(false);
162 
163             if (session == null || session.isNew())
164                 throw new StaleSessionException(EngineMessages.requestStateSession(stream),
165                         componentPage);
166         }
167 
168         Object[] parameters = linkFactory.extractListenerParameters(cycle);
169 
170         //this will also render the response
171         triggerComponent(cycle, stream, streamResourceSourceExtName, parameters);
172     }
173 
174     protected void triggerComponent(IRequestCycle cycle, IStream stream, String streamResourceSourceExtName, Object[] parameters) throws IOException {
175         IApplicationSpecification specification = cycle.getInfrastructure().getApplicationSpecification();
176 
177         if (streamResourceSourceExtName == null) {
178             streamResourceSourceExtName = DEFAULT_RESOURCE_SOURCE_NAME;
179         }
180         // ask the extension for the resource
181         StreamResourceSource source = (StreamResourceSource) specification.getExtension(BAYEUX_STREAM_EXTENTIONS_PREFIX + streamResourceSourceExtName, StreamResourceSource.class);
182 
183         if (LOG.isDebugEnabled()) {
184             int noParameters = 0;
185             if (parameters != null) {
186                 noParameters = parameters.length;
187             }
188             LOG.debug("going to process stream resource with " + noParameters + " parameter(s)");
189         }
190 
191         // ask the source for the resource to be streamed
192         StreamResource streamResource = source.getStreamResource(cycle, parameters);
193         if (streamResource == null) {
194             throw new ApplicationRuntimeException("unable to process stream, no suitable resource found.");
195         }
196 
197         streamResource(streamResource);
198 
199     }
200 
201     protected void streamResource(StreamResource resource) throws IOException {
202         if (LOG.isDebugEnabled()) {
203             LOG.debug("streaming resource with content-type '" + resource.getContentType() + "' and content-disposition '" + resource.getContentDisposition() + "'");
204         }
205         response.setContentType(resource.getContentType());
206         String contentDisposition = resource.getContentDisposition();
207         if (contentDisposition != null) {
208             response.setHeader("Content-disposition", contentDisposition);
209         }
210         OutputStream out = response.getOutputStream();
211         resource.writeTo(out);
212     }
213 
214     public void setRequest(WebRequest webRequest) {
215         this.webRequest = webRequest;
216     }
217 
218     public void setRequestCycle(IRequestCycle requestCycle) {
219         this.requestCycle = requestCycle;
220     }
221 }