View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.surf.mvc;
20  
21  import java.io.IOException;
22  
23  import javax.servlet.RequestDispatcher;
24  import javax.servlet.ServletException;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.springframework.extensions.surf.FrameworkUtil;
31  import org.springframework.extensions.surf.RequestContext;
32  import org.springframework.extensions.surf.WebFrameworkServiceRegistry;
33  import org.springframework.extensions.surf.util.WrappedHttpServletRequest;
34  import org.springframework.extensions.surf.util.WrappedHttpServletResponse;
35  import org.springframework.extensions.webscripts.servlet.mvc.ResourceController;
36  
37  /**
38   * Virtualized Spring controller for retrieving and serving
39   * resources.
40   * 
41   * This builds on the default resource controller provided by the
42   * Web Script Framework.  It empowers the controller to retrieve
43   * resources from remote CMIS stores ahead of the web application.
44   * This empowers the web application with preview capabilities.
45   * 
46   * This controller retrieves content by interrogating resource providers
47   * in the following order:
48   * 
49   * 1) CMIS Resource Store (i.e. Alfresco Store)
50   * 2) Web application path
51   * 3) Delegation to default url handler (typically the Spring JS resource servlet)
52   * 
53   * The first provider is only consulted if the Web Framework
54   * is running in preview mode.  If the Web Framework is configured
55   * to run in production mode, virtualized resource stores are
56   * never considered.
57   * 
58   * The following URL formats are supported:
59   * 
60   *    /resource/<path>?e=<endpointId>&s=<storeId>&<webappId>
61   *    /resource/<path>?e=<endpointId>&s=<storeId>
62   *    /resource/<path>?e=<endpointId>
63   *    /resource/<path>?s=<storeId>    
64   *    /resource/<path>
65   *
66   * In the latter cases, the web framework's selected store and webapp
67   * are determined from session and reused.  This enables the case for
68   * resources to be loaded as though they were local to disk (and located
69   * in the /resource context). 
70   *   
71   * @author muzquiano
72   */
73  public class VirtualizedResourceController extends ResourceController
74  {
75      private static Log logger = LogFactory.getLog(VirtualizedResourceController.class);
76          
77      // the web framework service registry
78      private WebFrameworkServiceRegistry webFrameworkServiceRegistry;
79      
80      /**
81       * Sets the service registry.
82       * 
83       * @param webFrameworkServiceRegistry the new service registry
84       */
85      public void setServiceRegistry(WebFrameworkServiceRegistry webFrameworkServiceRegistry)
86      {
87          this.webFrameworkServiceRegistry = webFrameworkServiceRegistry;
88      }
89      
90      /**
91       * Gets the service registry.
92       * 
93       * @return the service registry
94       */
95      public WebFrameworkServiceRegistry getServiceRegistry()
96      {
97          return this.webFrameworkServiceRegistry;
98      }
99      
100     /* (non-Javadoc)
101      * @see org.alfresco.web.framework.mvc.AbstractWebFrameworkController#getLogger()
102      */
103     public Log getLogger()
104     {
105         return logger;        
106     }
107 
108     @Override
109     /* (non-Javadoc)
110      * @see org.alfresco.web.scripts.servlet.mvc.ResourceController#dispatchResource(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
111      */
112     public boolean dispatchResource(String path, HttpServletRequest request, HttpServletResponse response)
113         throws ServletException, IOException
114     {
115         // are we in preview mode
116         boolean previewMode = this.getServiceRegistry().getWebFrameworkConfiguration().isPreviewEnabled();
117 
118         boolean resolved = false;
119         if (!resolved && previewMode)
120         {
121             // look up endpoint id, store id and webapp id
122             String endpointId = (String) request.getParameter("e");
123             String storeId = (String) request.getParameter("s");
124             String webappId = (String) request.getParameter("w");
125 
126             // see if we can resolve and serve back the remote resource            
127             resolved = retrieveRemoteResource(request, response, path, endpointId, storeId, webappId);
128         }
129         
130         if (!resolved)
131         {
132             // call to super class method which does all of the
133             // other lookup patterns, such as looking up in the web
134             // application context and web app jar files
135             
136             resolved = super.dispatchResource(path, request, response);
137         }
138         
139         return resolved;
140     }
141     
142     /**
143      * Retrieves content from the given remote store and streams back to the response
144      * 
145      * @param request http servlet request
146      * @param response http servlet response
147      * @param path the path to be included
148      * @param endpointId the endpoint to utilize (optional)
149      * @param storeId the store to utilize (optional)
150      * @param webappId the webapp to utilize (optional)
151      * 
152      * @return whether the resource was served back successfully
153      */
154     public boolean retrieveRemoteResource(HttpServletRequest request, HttpServletResponse response, String path, String endpointId, String storeId, String webappId)
155         throws ServletException, IOException
156     {
157         boolean resolved = false;
158         
159         // get the request context
160         RequestContext context = FrameworkUtil.getCurrentRequestContext();
161         
162         // default treatment of these fellows
163         if (endpointId == null)
164         {
165             endpointId = this.getServiceRegistry().getRemoteConfiguration().getDefaultEndpointId();
166         }
167         if (storeId != null)
168         {
169             // use the currently bound request context store id
170             storeId = context.getModel().getObjectManager().getContext().getStoreId();
171         }
172         if (webappId == null)
173         {
174             // use the currently bound request context web app id
175             webappId = context.getModel().getObjectManager().getContext().getWebappId();
176         }
177         
178         // check whether the resource exists on the remote store
179         // TODO: this is expensive... is this the cost of preview-able virtualized remote retrieval?
180         // TODO: ideally, we could hold a cache and have the authoring server notify test servers of changes, but that will require more investigation
181         boolean exists = checkRemoteResourceExists(context, request, response, path, endpointId, storeId, webappId);
182         if (exists)
183         {
184             // build a URL to the endpoint proxy servlet
185             StringBuilder fb = new StringBuilder(128);
186             fb.append(request.getServletPath());
187             fb.append("/endpoint/");
188             fb.append(endpointId);
189             fb.append("/avmstore/get/s/");
190             fb.append(storeId);
191             fb.append("/w/");
192             fb.append(webappId);
193         
194             if (!path.startsWith("/"))
195             {
196                 fb.append("/");
197             }
198             fb.append(path);
199             
200             String newUri = fb.toString();
201             
202             if (logger.isDebugEnabled())
203                 logger.debug("Formed virtual retrieval path: " + newUri);
204         
205             // make sure the request uri is properly established so that we can
206             // flow through the proxy servlet
207             if (request instanceof WrappedHttpServletRequest)
208             {
209                 ((WrappedHttpServletRequest)request).setRequestURI(request.getContextPath() + newUri);
210             }
211         
212             // dispatch to the endpoint proxy servlet
213             RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(newUri);        
214             dispatcher.include(request, response);
215             
216             resolved = true;
217         }
218         
219         return resolved;
220     }
221     
222     /**
223      * Checks for the existence of a resource on a remote store
224      * 
225      * @param context the request context
226      * @param request http servlet request
227      * @param response http servlet response
228      * @param path the path to the asset
229      * @param endpointId the endpoint where the resource lives
230      * @param storeId the store within which the resource lives
231      * @param webappId the web application that the resource lives in
232      * @return
233      * @throws ServletException
234      * @throws IOException
235      */
236     public boolean checkRemoteResourceExists(RequestContext context, HttpServletRequest request, HttpServletResponse response, String path, String endpointId, String storeId, String webappId)
237         throws ServletException, IOException
238     {
239         boolean exists = false;
240         
241         // default treatment of these fellows
242         if (endpointId == null)
243         {
244             endpointId = this.getServiceRegistry().getRemoteConfiguration().getDefaultEndpointId();
245         }
246         if (storeId != null)
247         {
248             // use the currently bound request context store id
249             storeId = context.getModel().getObjectManager().getContext().getStoreId();
250         }
251         if (webappId == null)
252         {
253             // use the currently bound request context web app id
254             webappId = context.getModel().getObjectManager().getContext().getWebappId();
255         }        
256         
257         // build a URL to the endpoint proxy servlet
258         StringBuilder fb = new StringBuilder(128);
259         fb.append(request.getServletPath());
260         fb.append("/endpoint/");
261         fb.append(endpointId);
262         fb.append("/avmstore/has/s/");
263         fb.append(storeId);
264         fb.append("/w/");
265         fb.append(webappId);
266     
267         if (!path.startsWith("/"))
268         {
269             fb.append("/");
270         }
271         fb.append(path);
272         
273         String newUri = fb.toString();
274         
275         if (logger.isDebugEnabled())
276             logger.debug("Formed virtual retrieval path: " + newUri);
277     
278         // build some wrapper objects so that we can dispatch to endpoint proxy servlet
279         // this means that we can avoid building connectors by hand
280         WrappedHttpServletRequest wrappedRequest = new WrappedHttpServletRequest(request);
281         WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse(response);
282 
283         // dispatch to the endpoint proxy servlet        
284         RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(newUri);        
285         dispatcher.include(wrappedRequest, wrappedResponse);
286 
287         // check whether the resource exists
288         String result = wrappedResponse.getOutput();
289         if ("true".equalsIgnoreCase(result))
290         {
291             exists = true;
292         }
293         
294         return exists;        
295     }
296 }