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