Coverage Report - org.apache.turbine.services.velocity.TurbineVelocityService
 
Classes in this File Line Coverage Branch Coverage Complexity
TurbineVelocityService
79%
113/142
60%
36/60
3,125
 
 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.OutputStream;
 26  
 import java.io.OutputStreamWriter;
 27  
 import java.io.Writer;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 
 31  
 import org.apache.commons.configuration2.Configuration;
 32  
 import org.apache.commons.lang3.StringUtils;
 33  
 import org.apache.logging.log4j.LogManager;
 34  
 import org.apache.logging.log4j.Logger;
 35  
 import org.apache.turbine.Turbine;
 36  
 import org.apache.turbine.pipeline.PipelineData;
 37  
 import org.apache.turbine.services.InitializationException;
 38  
 import org.apache.turbine.services.TurbineServices;
 39  
 import org.apache.turbine.services.pull.PullService;
 40  
 import org.apache.turbine.services.template.BaseTemplateEngineService;
 41  
 import org.apache.turbine.util.LocaleUtils;
 42  
 import org.apache.turbine.util.RunData;
 43  
 import org.apache.turbine.util.TurbineException;
 44  
 import org.apache.velocity.VelocityContext;
 45  
 import org.apache.velocity.app.VelocityEngine;
 46  
 import org.apache.velocity.app.event.EventCartridge;
 47  
 import org.apache.velocity.app.event.MethodExceptionEventHandler;
 48  
 import org.apache.velocity.context.Context;
 49  
 import org.apache.velocity.runtime.RuntimeConstants;
 50  
 import org.apache.velocity.util.introspection.Info;
 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  
  * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
 77  
  * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
 78  
  * @version $Id: TurbineVelocityService.java 1854787 2019-03-04 18:30:25Z tv $
 79  
  */
 80  75
 public class TurbineVelocityService
 81  
         extends BaseTemplateEngineService
 82  
         implements VelocityService,
 83  
                    MethodExceptionEventHandler
 84  
 {
 85  
     /** The generic resource loader path property in velocity.*/
 86  
     private static final String RESOURCE_LOADER_PATH = ".resource.loader.path";
 87  
 
 88  
     /** The prefix used for URIs which are of type <code>jar</code>. */
 89  
     private static final String JAR_PREFIX = "jar:";
 90  
 
 91  
     /** The prefix used for URIs which are of type <code>absolute</code>. */
 92  
     private static final String ABSOLUTE_PREFIX = "file://";
 93  
 
 94  
     /** Logging */
 95  75
     private static final Logger log = LogManager.getLogger(TurbineVelocityService.class);
 96  
 
 97  
     /** Encoding used when reading the templates. */
 98  
     private String defaultInputEncoding;
 99  
 
 100  
     /** Encoding used by the outputstream when handling the requests. */
 101  
     private String defaultOutputEncoding;
 102  
 
 103  
     /** Is the pullModelActive? */
 104  75
     private boolean pullModelActive = false;
 105  
 
 106  
     /** Shall we catch Velocity Errors and report them in the log file? */
 107  75
     private boolean catchErrors = true;
 108  
 
 109  
     /** Velocity runtime instance */
 110  75
     private VelocityEngine velocity = null;
 111  
 
 112  
     /** Internal Reference to the pull Service */
 113  75
     private PullService pullService = null;
 114  
 
 115  
 
 116  
     /**
 117  
      * Load all configured components and initialize them. This is
 118  
      * a zero parameter variant which queries the Turbine Servlet
 119  
      * for its config.
 120  
      *
 121  
      * @throws InitializationException Something went wrong in the init
 122  
      *         stage
 123  
      */
 124  
     @Override
 125  
     public void init()
 126  
             throws InitializationException
 127  
     {
 128  
         try
 129  
         {
 130  87
             velocity = getInitializedVelocityEngine();
 131  
 
 132  
             // We can only load the Pull Model ToolBox
 133  
             // if the Pull service has been listed in the TR.props
 134  
             // and the service has successfully been initialized.
 135  87
             if (TurbineServices.getInstance().isRegistered(PullService.SERVICE_NAME))
 136  
             {
 137  87
                 pullModelActive = true;
 138  87
                 pullService = (PullService)TurbineServices.getInstance().getService(PullService.SERVICE_NAME);
 139  
 
 140  87
                 log.debug("Activated Pull Tools");
 141  
             }
 142  
 
 143  
             // Register with the template service.
 144  87
             registerConfiguration(VelocityService.VELOCITY_EXTENSION);
 145  
 
 146  87
             defaultInputEncoding = getConfiguration().getString("input.encoding", LocaleUtils.getDefaultInputEncoding());
 147  
 
 148  87
             String outputEncoding = LocaleUtils.getOverrideCharSet();
 149  87
             if (outputEncoding == null)
 150  
             {
 151  87
                 outputEncoding = defaultInputEncoding;
 152  
             }
 153  87
             defaultOutputEncoding = getConfiguration().getString("output.encoding", defaultInputEncoding);
 154  
 
 155  87
             setInit(true);
 156  
         }
 157  0
         catch (Exception e)
 158  
         {
 159  0
             throw new InitializationException(
 160  
                 "Failed to initialize TurbineVelocityService", e);
 161  87
         }
 162  87
     }
 163  
 
 164  
     /**
 165  
      * Create a Context object that also contains the globalContext.
 166  
      *
 167  
      * @return A Context object.
 168  
      */
 169  
     @Override
 170  
     public Context getContext()
 171  
     {
 172  27
         Context globalContext =
 173  27
                 pullModelActive ? pullService.getGlobalContext() : null;
 174  
 
 175  27
         Context ctx = new VelocityContext(globalContext);
 176  27
         return ctx;
 177  
     }
 178  
 
 179  
     /**
 180  
      * This method returns a new, empty Context object.
 181  
      *
 182  
      * @return A Context Object.
 183  
      */
 184  
     @Override
 185  
     public Context getNewContext()
 186  
     {
 187  87
         Context ctx = new VelocityContext();
 188  
 
 189  
         // Attach an Event Cartridge to it, so we get exceptions
 190  
         // while invoking methods from the Velocity Screens
 191  87
         EventCartridge ec = new EventCartridge();
 192  87
         ec.addEventHandler(this);
 193  87
         ec.attachToContext(ctx);
 194  87
         return ctx;
 195  
     }
 196  
 
 197  
     /**
 198  
      * MethodException Event Cartridge handler
 199  
      * for Velocity.
 200  
      *
 201  
      * It logs an exception thrown by the velocity processing
 202  
      * on error level into the log file
 203  
      *
 204  
      * @param context The current context
 205  
      * @param clazz The class that threw the exception
 206  
      * @param method The Method name that threw the exception
 207  
      * @param e The exception that would've been thrown
 208  
      * @param info Information about the template, line and column the exception occurred
 209  
      * @return A valid value to be used as Return value
 210  
      */
 211  
     @Override
 212  
         public Object methodException(Context context, @SuppressWarnings("rawtypes") Class clazz, String method, Exception e, Info info)
 213  
     {
 214  0
         log.error("Class {}.{} threw Exception", clazz.getName(), method, e);
 215  
 
 216  0
         if (!catchErrors)
 217  
         {
 218  0
             throw new RuntimeException(e);
 219  
         }
 220  
 
 221  0
         return "[Turbine caught an Error in template " + info.getTemplateName()
 222  0
             + ", l:" + info.getLine()
 223  0
             + ", c:" + info.getColumn()
 224  
             + ". Look into the turbine.log for further information]";
 225  
     }
 226  
 
 227  
     /**
 228  
      * Create a Context from the PipelineData object.  Adds a pointer to
 229  
      * the PipelineData object to the VelocityContext so that PipelineData
 230  
      * is available in the templates.
 231  
      *
 232  
      * @param pipelineData The Turbine PipelineData object.
 233  
      * @return A clone of the WebContext needed by Velocity.
 234  
      */
 235  
     @Override
 236  
     public Context getContext(PipelineData pipelineData)
 237  
     {
 238  
         //Map runDataMap = (Map)pipelineData.get(RunData.class);
 239  84
         RunData data = (RunData)pipelineData;
 240  
         // Attempt to get it from the data first.  If it doesn't
 241  
         // exist, create it and then stuff it into the data.
 242  75
         Context context = (Context)
 243  75
             data.getTemplateInfo().getTemplateContext(VelocityService.CONTEXT);
 244  
 
 245  75
         if (context == null)
 246  
         {
 247  27
             context = getContext();
 248  27
             context.put(VelocityService.RUNDATA_KEY, data);
 249  
             // we will add both data and pipelineData to the context.
 250  27
             context.put(VelocityService.PIPELINEDATA_KEY, pipelineData);
 251  
 
 252  27
             if (pullModelActive)
 253  
             {
 254  
                 // Populate the toolbox with request scope, session scope
 255  
                 // and persistent scope tools (global tools are already in
 256  
                 // the toolBoxContent which has been wrapped to construct
 257  
                 // this request-specific context).
 258  27
                 pullService.populateContext(context, pipelineData);
 259  
             }
 260  
 
 261  27
             data.getTemplateInfo().setTemplateContext(
 262  
                 VelocityService.CONTEXT, context);
 263  
         }
 264  75
         return context;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Process the request and fill in the template with the values
 269  
      * you set in the Context.
 270  
      *
 271  
      * @param context  The populated context.
 272  
      * @param filename The file name of the template.
 273  
      * @return The process template as a String.
 274  
      *
 275  
      * @throws TurbineException Any exception thrown while processing will be
 276  
      *         wrapped into a TurbineException and rethrown.
 277  
      */
 278  
     @Override
 279  
     public String handleRequest(Context context, String filename)
 280  
         throws TurbineException
 281  
     {
 282  6
         String results = null;
 283  6
         OutputStreamWriter writer = null;
 284  6
         String charset = getOutputCharSet(context);
 285  
 
 286  6
         try (ByteArrayOutputStream bytes = new ByteArrayOutputStream())
 287  
         {
 288  6
             writer = new OutputStreamWriter(bytes, charset);
 289  
 
 290  6
             executeRequest(context, filename, writer);
 291  6
             writer.flush();
 292  6
             results = bytes.toString(charset);
 293  6
         }
 294  0
         catch (Exception e)
 295  
         {
 296  0
             renderingError(filename, e);
 297  6
         }
 298  
 
 299  6
         return results;
 300  
     }
 301  
 
 302  
     /**
 303  
      * Process the request and fill in the template with the values
 304  
      * you set in the Context.
 305  
      *
 306  
      * @param context A Context.
 307  
      * @param filename A String with the filename of the template.
 308  
      * @param output A OutputStream where we will write the process template as
 309  
      * a String.
 310  
      *
 311  
      * @throws TurbineException Any exception thrown while processing will be
 312  
      *         wrapped into a TurbineException and rethrown.
 313  
      */
 314  
     @Override
 315  
     public void handleRequest(Context context, String filename,
 316  
                               OutputStream output)
 317  
             throws TurbineException
 318  
     {
 319  6
         String charset  = getOutputCharSet(context);
 320  
 
 321  6
         try (OutputStreamWriter writer = new OutputStreamWriter(output, charset))
 322  
         {
 323  6
             executeRequest(context, filename, writer);
 324  6
         }
 325  0
         catch (Exception e)
 326  
         {
 327  0
             renderingError(filename, e);
 328  6
         }
 329  6
     }
 330  
 
 331  
     /**
 332  
      * Process the request and fill in the template with the values
 333  
      * you set in the Context.
 334  
      *
 335  
      * @param context A Context.
 336  
      * @param filename A String with the filename of the template.
 337  
      * @param writer A Writer where we will write the process template as
 338  
      * a String.
 339  
      *
 340  
      * @throws TurbineException Any exception thrown while processing will be
 341  
      *         wrapped into a TurbineException and rethrown.
 342  
      */
 343  
     @Override
 344  
     public void handleRequest(Context context, String filename, Writer writer)
 345  
             throws TurbineException
 346  
     {
 347  
         try
 348  
         {
 349  0
             executeRequest(context, filename, writer);
 350  
         }
 351  0
         catch (Exception e)
 352  
         {
 353  0
             renderingError(filename, e);
 354  
         }
 355  
         finally
 356  
         {
 357  0
             try
 358  
             {
 359  0
                 if (writer != null)
 360  
                 {
 361  0
                     writer.flush();
 362  
                 }
 363  
             }
 364  0
             catch (Exception ignored)
 365  
             {
 366  
                 // do nothing.
 367  0
             }
 368  0
         }
 369  0
     }
 370  
 
 371  
 
 372  
     /**
 373  
      * Process the request and fill in the template with the values
 374  
      * you set in the Context. Apply the character and template
 375  
      * encodings from RunData to the result.
 376  
      *
 377  
      * @param context A Context.
 378  
      * @param filename A String with the filename of the template.
 379  
      * @param writer A OutputStream where we will write the process template as
 380  
      * a String.
 381  
      *
 382  
      * @throws Exception A problem occurred.
 383  
      */
 384  
     private void executeRequest(Context context, String filename,
 385  
                                 Writer writer)
 386  
             throws Exception
 387  
     {
 388  12
         String encoding = getTemplateEncoding(context);
 389  
 
 390  12
         if (encoding == null)
 391  
         {
 392  0
           encoding = defaultOutputEncoding;
 393  
         }
 394  
 
 395  12
                 velocity.mergeTemplate(filename, encoding, context, writer);
 396  12
     }
 397  
 
 398  
     /**
 399  
      * Retrieve the required charset from the Turbine RunData in the context
 400  
      *
 401  
      * @param context A Context.
 402  
      * @return The character set applied to the resulting String.
 403  
      */
 404  
     private String getOutputCharSet(Context context)
 405  
     {
 406  12
         String charset = null;
 407  
 
 408  12
         Object data = context.get(VelocityService.RUNDATA_KEY);
 409  12
         if ((data != null) && (data instanceof RunData))
 410  
         {
 411  12
             charset = ((RunData) data).getCharSet();
 412  
         }
 413  
 
 414  12
         return (StringUtils.isEmpty(charset)) ? defaultOutputEncoding : charset;
 415  
     }
 416  
 
 417  
     /**
 418  
      * Retrieve the required encoding from the Turbine RunData in the context
 419  
      *
 420  
      * @param context A Context.
 421  
      * @return The encoding applied to the resulting String.
 422  
      */
 423  
     private String getTemplateEncoding(Context context)
 424  
     {
 425  12
         String encoding = null;
 426  
 
 427  12
         Object data = context.get(VelocityService.RUNDATA_KEY);
 428  12
         if ((data != null) && (data instanceof RunData))
 429  
         {
 430  12
             encoding = ((RunData) data).getTemplateEncoding();
 431  
         }
 432  
 
 433  12
         return encoding != null ? encoding : defaultInputEncoding;
 434  
     }
 435  
 
 436  
     /**
 437  
      * Macro to handle rendering errors.
 438  
      *
 439  
      * @param filename The file name of the unrenderable template.
 440  
      * @param e        The error.
 441  
      *
 442  
      * @throws TurbineException Thrown every time.  Adds additional
 443  
      *                             information to <code>e</code>.
 444  
      */
 445  
     private static final void renderingError(String filename, Exception e)
 446  
             throws TurbineException
 447  
     {
 448  0
         String err = "Error rendering Velocity template: " + filename;
 449  0
         log.error(err, e);
 450  0
         throw new TurbineException(err, e);
 451  
     }
 452  
 
 453  
     /**
 454  
      * Setup the velocity runtime by using a subset of the
 455  
      * Turbine configuration which relates to velocity.
 456  
      *
 457  
      * @throws Exception An Error occurred.
 458  
      * @return an initialized VelocityEngine instance
 459  
      */
 460  
     private VelocityEngine getInitializedVelocityEngine()
 461  
         throws Exception
 462  
     {
 463  
         // Get the configuration for this service.
 464  87
         Configuration conf = getConfiguration();
 465  
 
 466  87
         catchErrors = conf.getBoolean(CATCH_ERRORS_KEY, CATCH_ERRORS_DEFAULT);
 467  
 
 468  
         // backward compatibility, can be overridden in the configuration
 469  87
         conf.setProperty(RuntimeConstants.RUNTIME_LOG_NAME, "velocity");
 470  
 
 471  87
         VelocityEngine velocity = new VelocityEngine();
 472  87
         setVelocityProperties(velocity, conf);
 473  87
         velocity.init();
 474  
 
 475  87
         return velocity;
 476  
     }
 477  
 
 478  
 
 479  
     /**
 480  
      * This method generates the Properties object necessary
 481  
      * for the initialization of Velocity. It also converts the various
 482  
      * resource loader pathes into webapp relative pathes. It also
 483  
      *
 484  
      * @param velocity The Velocity engine
 485  
      * @param conf The Velocity Service configuration
 486  
      *
 487  
      * @throws Exception If a problem occurred while converting the properties.
 488  
      */
 489  
 
 490  
     protected void setVelocityProperties(VelocityEngine velocity, Configuration conf)
 491  
             throws Exception
 492  
     {
 493  
         // Fix up all the template resource loader pathes to be
 494  
         // webapp relative. Copy all other keys verbatim into the
 495  
         // veloConfiguration.
 496  
 
 497  90
         for (Iterator<String> i = conf.getKeys(); i.hasNext();)
 498  
         {
 499  1770
             String key = i.next();
 500  1770
             if (!key.endsWith(RESOURCE_LOADER_PATH))
 501  
             {
 502  1440
                 Object value = conf.getProperty(key);
 503  1440
                 if (value instanceof List<?>)
 504  
                 {
 505  0
                     for (Iterator<?> itr = ((List<?>)value).iterator(); itr.hasNext();)
 506  
                     {
 507  0
                         velocity.addProperty(key, itr.next());
 508  
                     }
 509  
                 }
 510  
                 else
 511  
                 {
 512  1440
                     velocity.addProperty(key, value);
 513  
                 }
 514  1440
                 continue; // for()
 515  
             }
 516  
 
 517  330
             List<Object> paths = conf.getList(key, null);
 518  330
             if (paths == null)
 519  
             {
 520  
                 // We don't copy this into VeloProperties, because
 521  
                 // null value is unhealthy for the ExtendedProperties object...
 522  0
                 continue; // for()
 523  
             }
 524  
 
 525  
             // Translate the supplied pathes given here.
 526  
             // the following three different kinds of
 527  
             // pathes must be translated to be webapp-relative
 528  
             //
 529  
             // jar:file://path-component!/entry-component
 530  
             // file://path-component
 531  
             // path/component
 532  330
             for (Object p : paths)
 533  
             {
 534  330
                     String path = (String)p;
 535  330
                 log.debug("Translating {}", path);
 536  
 
 537  330
                 if (path.startsWith(JAR_PREFIX))
 538  
                 {
 539  
                     // skip jar: -> 4 chars
 540  120
                     if (path.substring(4).startsWith(ABSOLUTE_PREFIX))
 541  
                     {
 542  
                         // We must convert up to the jar path separator
 543  90
                         int jarSepIndex = path.indexOf("!/");
 544  
 
 545  
                         // jar:file:// -> skip 11 chars
 546  90
                         path = (jarSepIndex < 0)
 547  30
                             ? Turbine.getRealPath(path.substring(11))
 548  
                         // Add the path after the jar path separator again to the new url.
 549  60
                             : (Turbine.getRealPath(path.substring(11, jarSepIndex)) + path.substring(jarSepIndex));
 550  
 
 551  90
                         log.debug("Result (absolute jar path): {}", path);
 552  90
                     }
 553  
                 }
 554  210
                 else if(path.startsWith(ABSOLUTE_PREFIX))
 555  
                 {
 556  
                     // skip file:// -> 7 chars
 557  30
                     path = Turbine.getRealPath(path.substring(7));
 558  
 
 559  30
                     log.debug("Result (absolute URL Path): {}", path);
 560  
                 }
 561  
                 // Test if this might be some sort of URL that we haven't encountered yet.
 562  180
                 else if(path.indexOf("://") < 0)
 563  
                 {
 564  150
                     path = Turbine.getRealPath(path);
 565  
 
 566  150
                     log.debug("Result (normal fs reference): {}", path);
 567  
                 }
 568  
 
 569  330
                 log.debug("Adding {} -> {}", key, path);
 570  
                 // Re-Add this property to the configuration object
 571  330
                 velocity.addProperty(key, path);
 572  330
             }
 573  330
         }
 574  90
     }
 575  
 
 576  
     /**
 577  
      * Find out if a given template exists. Velocity
 578  
      * will do its own searching to determine whether
 579  
      * a template exists or not.
 580  
      *
 581  
      * @param template String template to search for
 582  
      * @return True if the template can be loaded by Velocity
 583  
      */
 584  
     @Override
 585  
     public boolean templateExists(String template)
 586  
     {
 587  111
         return velocity.resourceExists(template);
 588  
     }
 589  
 
 590  
     /**
 591  
      * Performs post-request actions (releases context
 592  
      * tools back to the object pool).
 593  
      *
 594  
      * @param context a Velocity Context
 595  
      */
 596  
     @Override
 597  
     public void requestFinished(Context context)
 598  
     {
 599  6
         if (pullModelActive)
 600  
         {
 601  6
             pullService.releaseTools(context);
 602  
         }
 603  6
     }
 604  
 }