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.webscripts;
20  
21  import java.io.IOException;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
32  
33  
34  /**
35   * Encapsulates the execution of a single Web Script.
36   *
37   * Sub-classes of WebScriptRuntime maintain the execution environment e.g. servlet
38   * request & response.
39   * 
40   * A new instance of WebScriptRuntime is required for each invocation.
41   * 
42   * @author davidc
43   */
44  public abstract class AbstractRuntime implements Runtime
45  {
46      // Logger
47      protected static final Log logger = LogFactory.getLog(AbstractRuntime.class);
48  
49      /** Component Dependencies */
50      protected RuntimeContainer container;
51  
52      /**
53       * Construct
54       * 
55       * @param container  web script context
56       */
57      public AbstractRuntime(RuntimeContainer container)
58      {
59          this.container = container;
60      }
61      
62      /* (non-Javadoc)
63       * @see org.alfresco.web.scripts.Runtime#getContainer()
64       */
65      public Container getContainer()
66      {
67          return container;
68      }
69      
70      /**
71       * Execute the Web Script encapsulated by this Web Script Runtime
72       */
73      final public void executeScript()
74      {
75          long startRuntime = System.nanoTime();
76  
77          String method = getScriptMethod();
78          String scriptUrl = null;
79          Match match = null;
80  
81          try
82          {
83              // extract script url
84              scriptUrl = getScriptUrl();
85              if (scriptUrl == null || scriptUrl.length() == 0)
86              {
87                  throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Script URL not specified");
88              }
89  
90              if (logger.isDebugEnabled())
91                  logger.debug("(Runtime=" + getName() + ", Container=" + container.getName() + ") Processing script url ("  + method + ") " + scriptUrl);
92  
93              WebScriptRequest scriptReq = null;
94              WebScriptResponse scriptRes = null;
95              Authenticator auth = null;
96              
97              RequiredAuthentication containerRequiredAuth = container.getRequiredAuthentication();
98              
99              if (! containerRequiredAuth.equals(RequiredAuthentication.none))
100             {
101                 // Create initial request & response
102                 scriptReq = createRequest(null);
103                 scriptRes = createResponse();
104                 auth = createAuthenticator();
105                 
106                 if (logger.isDebugEnabled())
107                     logger.debug("(Runtime=" + getName() + ", Container=" + container.getName() + ") Container requires pre-auth: "+containerRequiredAuth);
108                 
109                 boolean preAuth = true;
110                 
111                 if (auth.emptyCredentials())
112                 {
113                     // check default (unauthenticated) domain
114                     match = container.getRegistry().findWebScript(method, scriptUrl);
115                     if ((match != null) && (match.getWebScript().getDescription().getRequiredAuthentication().equals(RequiredAuthentication.none)))
116                     {
117                         preAuth = false;
118                     }
119                 }
120                 
121                 if (preAuth && (! container.authenticate(auth, containerRequiredAuth)))
122                 {
123                     return; // return response (eg. prompt for un/pw if status is 401 or redirect)
124                 }
125             }
126             
127             if (match == null)
128             {
129                 match = container.getRegistry().findWebScript(method, scriptUrl);
130             }
131             
132             match = container.getRegistry().findWebScript(method, scriptUrl);
133             if (match == null || match.getKind() == Match.Kind.URI)
134             {
135                 if (match == null)
136                 {
137                     String msg = "Script url " + scriptUrl + " does not map to a Web Script.";
138                     if (logger.isDebugEnabled())
139                         logger.debug(msg);
140                     throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, msg);
141                 }
142                 else
143                 {
144                     String msg = "Script url " + scriptUrl + " does not support the method " + method;
145                     if (logger.isDebugEnabled())
146                         logger.debug(msg);
147                     throw new WebScriptException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
148                 }
149             }
150 
151             // create web script request & response
152             scriptReq = createRequest(match);
153             scriptRes = createResponse();
154             
155             if (auth == null)
156             {
157                 // not pre-authenticated
158                 auth = createAuthenticator();
159             }
160             
161             if (logger.isDebugEnabled())
162                 logger.debug("Agent: " + scriptReq.getAgent());
163 
164             long startScript = System.nanoTime();
165             final WebScript script = match.getWebScript();
166             final Description description = script.getDescription();
167             
168             try
169             {
170                 if (logger.isDebugEnabled())
171                 {
172                     String reqFormat = scriptReq.getFormat();
173                     String format = (reqFormat == null || reqFormat.length() == 0) ? "[undefined]" : reqFormat;
174                     Description desc = scriptReq.getServiceMatch().getWebScript().getDescription();
175                     logger.debug("Invoking Web Script " + description.getId() + " (format " + format + ", style: " + desc.getFormatStyle() + ", default: " + desc.getDefaultFormat() + ")");
176                 }
177 
178                 executeScript(scriptReq, scriptRes, auth);
179             }
180             finally
181             {
182                 if (logger.isDebugEnabled())
183                 {
184                     long endScript = System.nanoTime();
185                     logger.debug("Web Script " + description.getId() + " executed in " + (endScript - startScript)/1000000f + "ms");
186                 }
187             }
188         }
189         catch(Throwable e)
190         {
191         	// log error on server so its not swallowed and lost
192             if (logger.isErrorEnabled())
193             {
194                 logger.error("Exception from executeScript - redirecting to status template error: " + e.getMessage(), e);
195             }
196 
197             // setup context
198             WebScriptRequest req = createRequest(null);
199             WebScriptResponse res = createResponse();
200 
201             // extract status code, if specified
202             int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
203             StatusTemplate statusTemplate = null;
204             Map<String, Object> statusModel = null;
205             if (e instanceof WebScriptException)
206             {
207                 WebScriptException we = (WebScriptException)e;
208                 statusCode = we.getStatus();
209                 statusTemplate = we.getStatusTemplate();
210                 statusModel = we.getStatusModel();
211             }
212 
213             // retrieve status template for response rendering
214             if (statusTemplate == null)
215             {
216                 // locate status template
217                 // NOTE: search order...
218                 //   1) root located <status>.ftl
219                 //   2) root located status.ftl
220                 statusTemplate = getStatusCodeTemplate(statusCode);
221                 
222                 String validTemplatePath = container.getTemplateProcessorRegistry().findValidTemplatePath(statusTemplate.getPath());
223                 if (validTemplatePath == null)
224                 {
225                     statusTemplate = getStatusTemplate();
226                     
227                     validTemplatePath = container.getTemplateProcessorRegistry().findValidTemplatePath(statusTemplate.getPath());
228                     if (validTemplatePath == null)
229                     {
230                         throw new WebScriptException("Failed to find status template " + statusTemplate.getPath() + " (format: " + statusTemplate.getFormat() + ")");
231                     }
232                 }                
233             }
234 
235             // create basic model for all information known at this point, if one hasn't been pre-provided
236             if (statusModel == null || statusModel.equals(Collections.EMPTY_MAP))
237             {
238                 statusModel = new HashMap<String, Object>(8, 1.0f);
239                 statusModel.put("url", new URLModel(req));
240                 statusModel.put("server", container.getDescription());
241                 statusModel.put("date", new Date());
242                 if (match != null && match.getWebScript() != null)
243                 {
244                     statusModel.put("webscript", match.getWebScript().getDescription());
245                 }
246             }
247 
248             // add status to model
249             Status status = new Status();
250             status.setCode(statusCode);
251             status.setMessage(e.getMessage() != null ? e.getMessage() : e.toString());
252             status.setException(e);
253             statusModel.put("status", status);
254 
255             // render output
256             String mimetype = container.getFormatRegistry().getMimeType(req.getAgent(), statusTemplate.getFormat());
257             if (mimetype == null)
258             {
259                 throw new WebScriptException("Web Script format '" + statusTemplate.getFormat() + "' is not registered");
260             }
261             
262             if (logger.isDebugEnabled())
263             {
264                 logger.debug("Force success status header in response: " + req.forceSuccessStatus());
265                 logger.debug("Sending status " + statusCode + " (Template: " + statusTemplate.getPath() + ")");
266                 logger.debug("Rendering response: content type=" + mimetype);
267             }
268 
269             res.reset();
270             Cache cache = new Cache();
271             cache.setNeverCache(true);
272             res.setCache(cache);
273             res.setStatus(req.forceSuccessStatus() ? HttpServletResponse.SC_OK : statusCode);
274             res.setContentType(Format.HTML.mimetype() + ";charset=UTF-8");
275             try
276             {
277                 String validTemplatePath = container.getTemplateProcessorRegistry().findValidTemplatePath(statusTemplate.getPath());                
278                 TemplateProcessor statusProcessor = container.getTemplateProcessorRegistry().getTemplateProcessor(validTemplatePath);
279                 statusProcessor.process(validTemplatePath, statusModel, res.getWriter());
280             }
281             catch (Exception e1)
282             {
283                 logger.error("Internal error", e1);
284                 throw new WebScriptException("Internal error", e1);
285             }
286         }
287         finally
288         {
289             long endRuntime = System.nanoTime();
290             if (logger.isDebugEnabled())
291                 logger.debug("Processed script url ("  + method + ") " + scriptUrl + " in " + (endRuntime - startRuntime)/1000000f + "ms");
292         }
293     }
294 
295     /**
296      * Execute script given the specified context
297      * 
298      * @param scriptReq
299      * @param scriptRes
300      * @param auth
301      * 
302      * @throws IOException
303      */
304     protected void executeScript(WebScriptRequest scriptReq, WebScriptResponse scriptRes, Authenticator auth)
305         throws IOException
306     {
307         container.executeScript(scriptReq, scriptRes, auth);
308     }
309 
310     /**
311      * Get code specific Status Template path
312      * 
313      * @param statusCode
314      * @return  path
315      */
316     protected StatusTemplate getStatusCodeTemplate(int statusCode)
317     {
318         return new StatusTemplate("/" + statusCode + ".ftl", WebScriptResponse.HTML_FORMAT);
319     }
320     
321     /**
322      * Get Status Template path
323      * 
324      * @return  path
325      */
326     protected StatusTemplate getStatusTemplate()
327     {
328         return new StatusTemplate("/status.ftl", WebScriptResponse.HTML_FORMAT);
329     }
330 
331     /* (non-Javadoc)
332      * @see org.alfresco.web.scripts.Runtime#getScriptParameters()
333      */
334     public Map<String, Object> getScriptParameters()
335     {
336         return Collections.emptyMap();
337     }
338 
339     /* (non-Javadoc)
340      * @see org.alfresco.web.scripts.Runtime#getTemplateParameters()
341      */
342     public Map<String, Object> getTemplateParameters()
343     {
344         return Collections.emptyMap();
345     }
346     
347     /**
348      * Get the Web Script Method  e.g. get, post
349      * 
350      * @return  web script method
351      */
352     protected abstract String getScriptMethod();
353 
354     /**
355      * Get the Web Script Url
356      * 
357      * @return  web script url
358      */
359     protected abstract String getScriptUrl();
360     
361     /**
362      * Create a Web Script Request
363      * 
364      * @param match  web script matching the script method and url
365      * @return  web script request
366      */
367     protected abstract WebScriptRequest createRequest(Match match);
368     
369     /**
370      * Create a Web Script Response
371      * 
372      * @return  web script response
373      */
374     protected abstract WebScriptResponse createResponse();
375     
376     /**
377      * Create a Web Script Authenticator
378      * 
379      * @return  web script authenticator
380      */
381     protected abstract Authenticator createAuthenticator();
382     
383 
384     /**
385      * Helper to retrieve real (last) Web Script Request in a stack of wrapped Web Script requests
386      * 
387      * @param request
388      * @return
389      */
390     protected static WebScriptRequest getRealWebScriptRequest(WebScriptRequest request)
391     {
392         WebScriptRequest real = request;
393         while(real instanceof WrappingWebScriptRequest)
394         {
395             real = ((WrappingWebScriptRequest)real).getNext();
396         }
397         return real;
398     }
399 
400     /**
401      * Helper to retrieve real (last) Web Script Response in a stack of wrapped Web Script responses
402      * 
403      * @param response
404      * @return
405      */
406     protected static WebScriptResponse getRealWebScriptResponse(WebScriptResponse response)
407     {
408         WebScriptResponse real = response;
409         while(real instanceof WrappingWebScriptResponse)
410         {
411             real = ((WrappingWebScriptResponse)real).getNext();
412         }
413         return real;
414     }
415 
416 }