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