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