Coverage report

  %line %branch
org.apache.turbine.services.velocity.TurbineVelocityService
39% 
55% 

 1  
 package org.apache.turbine.services.velocity;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2005 The Apache Software Foundation.
 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  
 import java.io.ByteArrayOutputStream;
 20  
 import java.io.IOException;
 21  
 import java.io.OutputStream;
 22  
 import java.io.OutputStreamWriter;
 23  
 import java.io.Writer;
 24  
 
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import java.util.Vector;
 28  
 
 29  
 import javax.servlet.ServletConfig;
 30  
 
 31  
 import org.apache.commons.collections.ExtendedProperties;
 32  
 import org.apache.commons.configuration.Configuration;
 33  
 import org.apache.commons.lang.StringUtils;
 34  
 import org.apache.commons.logging.Log;
 35  
 import org.apache.commons.logging.LogFactory;
 36  
 
 37  
 import org.apache.velocity.VelocityContext;
 38  
 import org.apache.velocity.app.Velocity;
 39  
 import org.apache.velocity.app.event.EventCartridge;
 40  
 import org.apache.velocity.app.event.MethodExceptionEventHandler;
 41  
 import org.apache.velocity.context.Context;
 42  
 import org.apache.velocity.runtime.log.SimpleLog4JLogSystem;
 43  
 
 44  
 import org.apache.turbine.Turbine;
 45  
 import org.apache.turbine.services.InitializationException;
 46  
 import org.apache.turbine.services.pull.PullService;
 47  
 import org.apache.turbine.services.pull.TurbinePull;
 48  
 import org.apache.turbine.services.template.BaseTemplateEngineService;
 49  
 import org.apache.turbine.util.RunData;
 50  
 import org.apache.turbine.util.TurbineException;
 51  
 
 52  
 /**
 53  
  * This is a Service that can process Velocity templates from within a
 54  
  * Turbine Screen. It is used in conjunction with the templating service
 55  
  * as a Templating Engine for templates ending in "vm". It registers
 56  
  * itself as translation engine with the template service and gets
 57  
  * accessed from there. After configuring it in your properties, it
 58  
  * should never be necessary to call methods from this service directly.
 59  
  *
 60  
  * Here's an example of how you might use it from a
 61  
  * screen:<br>
 62  
  *
 63  
  * <code>
 64  
  * Context context = TurbineVelocity.getContext(data);<br>
 65  
  * context.put("message", "Hello from Turbine!");<br>
 66  
  * String results = TurbineVelocity.handleRequest(context,"helloWorld.vm");<br>
 67  
  * data.getPage().getBody().addElement(results);<br>
 68  
  * </code>
 69  
  *
 70  
  * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
 71  
  * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
 72  
  * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
 73  
  * @author <a href="mailto:sean@informage.ent">Sean Legassick</a>
 74  
  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
 75  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 76  
  * @version $Id: TurbineVelocityService.java 264152 2005-08-29 14:50:22Z henning $
 77  
  */
 78  100
 public class TurbineVelocityService
 79  
         extends BaseTemplateEngineService
 80  
         implements VelocityService,
 81  
                    MethodExceptionEventHandler
 82  
 {
 83  
     /** The generic resource loader path property in velocity.*/
 84  
     private static final String RESOURCE_LOADER_PATH = ".resource.loader.path";
 85  
 
 86  
     /** Default character set to use if not specified in the RunData object. */
 87  
     private static final String DEFAULT_CHAR_SET = "ISO-8859-1";
 88  
 
 89  
     /** The prefix used for URIs which are of type <code>jar</code>. */
 90  
     private static final String JAR_PREFIX = "jar:";
 91  
 
 92  
     /** The prefix used for URIs which are of type <code>absolute</code>. */
 93  
     private static final String ABSOLUTE_PREFIX = "file://";
 94  
 
 95  
     /** Logging */
 96  80
     private static Log log = LogFactory.getLog(TurbineVelocityService.class);
 97  
 
 98  
     /** Is the pullModelActive? */
 99  40
     private boolean pullModelActive = false;
 100  
 
 101  
     /** Shall we catch Velocity Errors and report them in the log file? */
 102  40
     private boolean catchErrors = true;
 103  
 
 104  
     /** Internal Reference to the pull Service */
 105  40
     private PullService pullService = null;
 106  
 
 107  
 
 108  
     /**
 109  
      * Load all configured components and initialize them. This is
 110  
      * a zero parameter variant which queries the Turbine Servlet
 111  
      * for its config.
 112  
      *
 113  
      * @throws InitializationException Something went wrong in the init
 114  
      *         stage
 115  
      */
 116  
     public void init()
 117  
             throws InitializationException
 118  
     {
 119  
         try
 120  
         {
 121  42
             initVelocity();
 122  
 
 123  
             // We can only load the Pull Model ToolBox
 124  
             // if the Pull service has been listed in the TR.props
 125  
             // and the service has successfully been initialized.
 126  42
             if (TurbinePull.isRegistered())
 127  
             {
 128  42
                 pullModelActive = true;
 129  
 
 130  42
                 pullService = TurbinePull.getService();
 131  
 
 132  42
                 log.debug("Activated Pull Tools");
 133  
             }
 134  
 
 135  
             // Register with the template service.
 136  42
             registerConfiguration(VelocityService.VELOCITY_EXTENSION);
 137  
 
 138  42
             setInit(true);
 139  21
         }
 140  0
         catch (Exception e)
 141  
         {
 142  0
             throw new InitializationException(
 143  
                 "Failed to initialize TurbineVelocityService", e);
 144  21
         }
 145  42
     }
 146  
 
 147  
 
 148  
     /**
 149  
      * Inits the service using servlet parameters to obtain path to the
 150  
      * configuration file.
 151  
      *
 152  
      * @param config The ServletConfiguration from Turbine
 153  
      *
 154  
      * @throws InitializationException Something went wrong when starting up.
 155  
      * @deprecated use init() instead.
 156  
      */
 157  
     public void init(ServletConfig config)
 158  
             throws InitializationException
 159  
     {
 160  0
         init();
 161  0
     }
 162  
 
 163  
 
 164  
     /**
 165  
      * Create a Context object that also contains the globalContext.
 166  
      *
 167  
      * @return A Context object.
 168  
      */
 169  
     public Context getContext()
 170  
     {
 171  0
         Context globalContext =
 172  
                 pullModelActive ? pullService.getGlobalContext() : null;
 173  
 
 174  0
         Context ctx = new VelocityContext(globalContext);
 175  0
         return ctx;
 176  
     }
 177  
 
 178  
     /**
 179  
      * This method returns a new, empty Context object.
 180  
      *
 181  
      * @return A Context Object.
 182  
      */
 183  
     public Context getNewContext()
 184  
     {
 185  42
         Context ctx = new VelocityContext();
 186  
 
 187  
         // Attach an Event Cartridge to it, so we get exceptions
 188  
         // while invoking methods from the Velocity Screens
 189  42
         EventCartridge ec = new EventCartridge();
 190  42
         ec.addEventHandler(this);
 191  42
         ec.attachToContext(ctx);
 192  42
         return ctx;
 193  
     }
 194  
 
 195  
     /**
 196  
      * MethodException Event Cartridge handler
 197  
      * for Velocity.
 198  
      *
 199  
      * It logs an execption thrown by the velocity processing
 200  
      * on error level into the log file
 201  
      *
 202  
      * @param clazz The class that threw the exception
 203  
      * @param method The Method name that threw the exception
 204  
      * @param e The exception that would've been thrown
 205  
      * @return A valid value to be used as Return value
 206  
      * @throws Exception We threw the exception further up
 207  
      */
 208  
     public Object methodException(Class clazz, String method, Exception e)
 209  
             throws Exception
 210  
     {
 211  0
         log.error("Class " + clazz.getName() + "." + method + " threw Exception", e);
 212  
 
 213  0
         if (!catchErrors)
 214  
         {
 215  0
             throw e;
 216  
         }
 217  
 
 218  0
         return "[Turbine caught an Error here. Look into the turbine.log for further information]";
 219  
     }
 220  
 
 221  
     /**
 222  
      * Create a Context from the RunData object.  Adds a pointer to
 223  
      * the RunData object to the VelocityContext so that RunData
 224  
      * is available in the templates.
 225  
      *
 226  
      * @param data The Turbine RunData object.
 227  
      * @return A clone of the WebContext needed by Velocity.
 228  
      */
 229  
     public Context getContext(RunData data)
 230  
     {
 231  
         // Attempt to get it from the data first.  If it doesn't
 232  
         // exist, create it and then stuff it into the data.
 233  0
         Context context = (Context)
 234  
             data.getTemplateInfo().getTemplateContext(VelocityService.CONTEXT);
 235  
 
 236  0
         if (context == null)
 237  
         {
 238  0
             context = getContext();
 239  0
             context.put(VelocityService.RUNDATA_KEY, data);
 240  
 
 241  0
             if (pullModelActive)
 242  
             {
 243  
                 // Populate the toolbox with request scope, session scope
 244  
                 // and persistent scope tools (global tools are already in
 245  
                 // the toolBoxContent which has been wrapped to construct
 246  
                 // this request-specific context).
 247  0
                 pullService.populateContext(context, data);
 248  
             }
 249  
 
 250  0
             data.getTemplateInfo().setTemplateContext(
 251  
                 VelocityService.CONTEXT, context);
 252  
         }
 253  0
         return context;
 254  
     }
 255  
 
 256  
     /**
 257  
      * Process the request and fill in the template with the values
 258  
      * you set in the Context.
 259  
      *
 260  
      * @param context  The populated context.
 261  
      * @param filename The file name of the template.
 262  
      * @return The process template as a String.
 263  
      *
 264  
      * @throws TurbineException Any exception trown while processing will be
 265  
      *         wrapped into a TurbineException and rethrown.
 266  
      */
 267  
     public String handleRequest(Context context, String filename)
 268  
         throws TurbineException
 269  
     {
 270  0
         String results = null;
 271  0
         ByteArrayOutputStream bytes = null;
 272  0
         OutputStreamWriter writer = null;
 273  0
         String charset = getCharSet(context);
 274  
 
 275  
         try
 276  
         {
 277  0
             bytes = new ByteArrayOutputStream();
 278  
 
 279  0
             writer = new OutputStreamWriter(bytes, charset);
 280  
 
 281  0
             executeRequest(context, filename, writer);
 282  0
             writer.flush();
 283  0
             results = bytes.toString(charset);
 284  0
         }
 285  0
         catch (Exception e)
 286  
         {
 287  0
             renderingError(filename, e);
 288  0
         }
 289  
         finally
 290  
         {
 291  0
             try
 292  
             {
 293  0
                 if (bytes != null)
 294  
                 {
 295  0
                     bytes.close();
 296  
                 }
 297  
             }
 298  0
             catch (IOException ignored)
 299  
             {
 300  
                 // do nothing.
 301  0
             }
 302  0
         }
 303  0
         return results;
 304  
     }
 305  
 
 306  
     /**
 307  
      * Process the request and fill in the template with the values
 308  
      * you set in the Context.
 309  
      *
 310  
      * @param context A Context.
 311  
      * @param filename A String with the filename of the template.
 312  
      * @param output A OutputStream where we will write the process template as
 313  
      * a String.
 314  
      *
 315  
      * @throws TurbineException Any exception trown while processing will be
 316  
      *         wrapped into a TurbineException and rethrown.
 317  
      */
 318  
     public void handleRequest(Context context, String filename,
 319  
                               OutputStream output)
 320  
             throws TurbineException
 321  
     {
 322  0
         String charset  = getCharSet(context);
 323  0
         OutputStreamWriter writer = null;
 324  
 
 325  
         try
 326  
         {
 327  0
             writer = new OutputStreamWriter(output, charset);
 328  0
             executeRequest(context, filename, writer);
 329  0
         }
 330  0
         catch (Exception e)
 331  
         {
 332  0
             renderingError(filename, e);
 333  0
         }
 334  
         finally
 335  
         {
 336  0
             try
 337  
             {
 338  0
                 if (writer != null)
 339  
                 {
 340  0
                     writer.flush();
 341  
                 }
 342  
             }
 343  0
             catch (Exception ignored)
 344  
             {
 345  
                 // do nothing.
 346  0
             }
 347  0
         }
 348  0
     }
 349  
 
 350  
 
 351  
     /**
 352  
      * Process the request and fill in the template with the values
 353  
      * you set in the Context.
 354  
      *
 355  
      * @param context A Context.
 356  
      * @param filename A String with the filename of the template.
 357  
      * @param writer A Writer where we will write the process template as
 358  
      * a String.
 359  
      *
 360  
      * @throws TurbineException Any exception trown while processing will be
 361  
      *         wrapped into a TurbineException and rethrown.
 362  
      */
 363  
     public void handleRequest(Context context, String filename, Writer writer)
 364  
             throws TurbineException
 365  
     {
 366  
         try
 367  
         {
 368  0
             executeRequest(context, filename, writer);
 369  0
         }
 370  0
         catch (Exception e)
 371  
         {
 372  0
             renderingError(filename, e);
 373  0
         }
 374  
         finally
 375  
         {
 376  0
             try
 377  
             {
 378  0
                 if (writer != null)
 379  
                 {
 380  0
                     writer.flush();
 381  
                 }
 382  
             }
 383  0
             catch (Exception ignored)
 384  
             {
 385  
                 // do nothing.
 386  0
             }
 387  0
         }
 388  0
     }
 389  
 
 390  
 
 391  
     /**
 392  
      * Process the request and fill in the template with the values
 393  
      * you set in the Context. Apply the character and template
 394  
      * encodings from RunData to the result.
 395  
      *
 396  
      * @param context A Context.
 397  
      * @param filename A String with the filename of the template.
 398  
      * @param writer A OutputStream where we will write the process template as
 399  
      * a String.
 400  
      *
 401  
      * @throws Exception A problem occured.
 402  
      */
 403  
     private void executeRequest(Context context, String filename,
 404  
                                 Writer writer)
 405  
             throws Exception
 406  
     {
 407  0
         String encoding = getEncoding(context);
 408  
 
 409  0
         if (encoding != null)
 410  
         {
 411  0
             Velocity.mergeTemplate(filename, encoding, context, writer);
 412  
         }
 413  
         else
 414  
         {
 415  0
             Velocity.mergeTemplate(filename, context, writer);
 416  
         }
 417  0
     }
 418  
 
 419  
     /**
 420  
      * Retrieve the required charset from the Turbine RunData in the context
 421  
      *
 422  
      * @param context A Context.
 423  
      * @return The character set applied to the resulting String.
 424  
      */
 425  
     private String getCharSet(Context context)
 426  
     {
 427  0
         String charset = null;
 428  
 
 429  0
         Object data = context.get(VelocityService.RUNDATA_KEY);
 430  0
         if ((data != null) && (data instanceof RunData))
 431  
         {
 432  0
             charset = ((RunData) data).getCharSet();
 433  
         }
 434  
 
 435  0
         return (StringUtils.isEmpty(charset)) ? DEFAULT_CHAR_SET : charset;
 436  
     }
 437  
 
 438  
     /**
 439  
      * Retrieve the required encoding from the Turbine RunData in the context
 440  
      *
 441  
      * @param context A Context.
 442  
      * @return The encoding applied to the resulting String.
 443  
      */
 444  
     private String getEncoding(Context context)
 445  
     {
 446  0
         String encoding = null;
 447  
 
 448  0
         Object data = context.get(VelocityService.RUNDATA_KEY);
 449  0
         if ((data != null) && (data instanceof RunData))
 450  
         {
 451  0
             encoding = ((RunData) data).getTemplateEncoding();
 452  
         }
 453  
 
 454  0
         return encoding;
 455  
     }
 456  
 
 457  
     /**
 458  
      * Macro to handle rendering errors.
 459  
      *
 460  
      * @param filename The file name of the unrenderable template.
 461  
      * @param e        The error.
 462  
      *
 463  
      * @exception TurbineException Thrown every time.  Adds additional
 464  
      *                             information to <code>e</code>.
 465  
      */
 466  
     private static void renderingError(String filename, Exception e)
 467  
             throws TurbineException
 468  
     {
 469  0
         String err = "Error rendering Velocity template: " + filename;
 470  0
         log.error(err, e);
 471  0
         throw new TurbineException(err, e);
 472  
     }
 473  
 
 474  
     /**
 475  
      * Setup the velocity runtime by using a subset of the
 476  
      * Turbine configuration which relates to velocity.
 477  
      *
 478  
      * @exception Exception An Error occured.
 479  
      */
 480  
     private synchronized void initVelocity()
 481  
         throws Exception
 482  
     {
 483  
         // Get the configuration for this service.
 484  42
         Configuration conf = getConfiguration();
 485  
 
 486  42
         catchErrors = conf.getBoolean(CATCH_ERRORS_KEY, CATCH_ERRORS_DEFAULT);
 487  
 
 488  42
         conf.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS,
 489  
                 SimpleLog4JLogSystem.class.getName());
 490  42
         conf.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM
 491  
                 + ".log4j.category", "velocity");
 492  
 
 493  42
         Velocity.setExtendedProperties(createVelocityProperties(conf));
 494  42
         Velocity.init();
 495  42
     }
 496  
 
 497  
 
 498  
     /**
 499  
      * This method generates the Extended Properties object necessary
 500  
      * for the initialization of Velocity. It also converts the various
 501  
      * resource loader pathes into webapp relative pathes. It also
 502  
      *
 503  
      * @param conf The Velocity Service configuration
 504  
      *
 505  
      * @return An ExtendedProperties Object for Velocity
 506  
      *
 507  
      * @throws Exception If a problem occured while converting the properties.
 508  
      */
 509  
 
 510  
     public ExtendedProperties createVelocityProperties(Configuration conf)
 511  
             throws Exception
 512  
     {
 513  
         // This bugger is public, because we want to run some Unit tests
 514  
         // on it.
 515  
 
 516  44
         ExtendedProperties veloConfig = new ExtendedProperties();
 517  
 
 518  
         // Fix up all the template resource loader pathes to be
 519  
         // webapp relative. Copy all other keys verbatim into the
 520  
         // veloConfiguration.
 521  
 
 522  568
         for (Iterator i = conf.getKeys(); i.hasNext();)
 523  
         {
 524  1004
             String key = (String) i.next();
 525  1004
             if (!key.endsWith(RESOURCE_LOADER_PATH))
 526  
             {
 527  816
                 Object value = conf.getProperty(key);
 528  
 
 529  
                 // Since 1.0-pre-something, Commons Collections suddently
 530  
                 // no longer returns a vector for multiple value-keys but a
 531  
                 // List object. Velocity will choke if we add this object because
 532  
                 // org.apache.commons.collections.ExtendedProperties expect a
 533  
                 // Vector object. Ah, the joys of incompatible class changes,
 534  
                 // unwritten assumptions and general Java JAR Hell... =8-O
 535  816
                 if (value instanceof List)
 536  
                 {
 537  26
                     List srcValue = (List) value;
 538  26
                     Vector targetValue = new Vector(srcValue.size());
 539  
 
 540  65
                     for (Iterator it = srcValue.iterator(); it.hasNext(); )
 541  
                     {
 542  52
                         targetValue.add(it.next());
 543  
                     }
 544  
 
 545  26
                     veloConfig.addProperty(key, targetValue);
 546  
                 }
 547  
                 else
 548  
                 {
 549  790
                     veloConfig.addProperty(key, value);
 550  
                 }
 551  
 
 552  790
                 continue; // for()
 553  
             }
 554  
 
 555  188
             List paths = conf.getList(key, null);
 556  188
             if (paths == null)
 557  
             {
 558  
                 // We don't copy this into VeloProperties, because
 559  
                 // null value is unhealthy for the ExtendedProperties object...
 560  0
                 continue; // for()
 561  
             }
 562  
 
 563  188
             Velocity.clearProperty(key);
 564  
 
 565  
             // Translate the supplied pathes given here.
 566  
             // the following three different kinds of
 567  
             // pathes must be translated to be webapp-relative
 568  
             //
 569  
             // jar:file://path-component!/entry-component
 570  
             // file://path-component
 571  
             // path/component
 572  
 
 573  376
             for (Iterator j = paths.iterator(); j.hasNext();)
 574  
             {
 575  188
                 String path = (String) j.next();
 576  
 
 577  188
                 log.debug("Translating " + path);
 578  
 
 579  188
                 if (path.startsWith(JAR_PREFIX))
 580  
                 {
 581  
                     // skip jar: -> 4 chars
 582  72
                     if (path.substring(4).startsWith(ABSOLUTE_PREFIX))
 583  
                     {
 584  
                         // We must convert up to the jar path separator
 585  54
                         int jarSepIndex = path.indexOf("!/");
 586  
 
 587  
                         // jar:file:// -> skip 11 chars
 588  54
                         path = (jarSepIndex < 0)
 589  
                             ? Turbine.getRealPath(path.substring(11))
 590  
                         // Add the path after the jar path separator again to the new url.
 591  
                             : (Turbine.getRealPath(path.substring(11, jarSepIndex)) + path.substring(jarSepIndex));
 592  
 
 593  54
                         log.debug("Result (absolute jar path): " + path);
 594  
                     }
 595  
                 }
 596  116
                 else if (path.startsWith(ABSOLUTE_PREFIX))
 597  
                 {
 598  
                     // skip file:// -> 7 chars
 599  18
                     path = Turbine.getRealPath(path.substring(7));
 600  
 
 601  18
                     log.debug("Result (absolute URL Path): " + path);
 602  
                 }
 603  
                 // Test if this might be some sort of URL that we haven't encountered yet.
 604  98
                 else if (path.indexOf("://") < 0)
 605  
                 {
 606  80
                     path = Turbine.getRealPath(path);
 607  
 
 608  80
                     log.debug("Result (normal fs reference): " + path);
 609  
                 }
 610  
 
 611  188
                 log.debug("Adding " + key + " -> " + path);
 612  
                 // Re-Add this property to the configuration object
 613  188
                 veloConfig.addProperty(key, path);
 614  
             }
 615  
         }
 616  44
         return veloConfig;
 617  
     }
 618  
 
 619  
     /**
 620  
      * Find out if a given template exists. Velocity
 621  
      * will do its own searching to determine whether
 622  
      * a template exists or not.
 623  
      *
 624  
      * @param template String template to search for
 625  
      * @return True if the template can be loaded by Velocity
 626  
      */
 627  
     public boolean templateExists(String template)
 628  
     {
 629  46
         return Velocity.templateExists(template);
 630  
     }
 631  
 
 632  
     /**
 633  
      * Performs post-request actions (releases context
 634  
      * tools back to the object pool).
 635  
      *
 636  
      * @param context a Velocity Context
 637  
      */
 638  
     public void requestFinished(Context context)
 639  
     {
 640  0
         if (pullModelActive)
 641  
         {
 642  0
             pullService.releaseTools(context);
 643  
         }
 644  0
     }
 645  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.