View Javadoc

1   package org.apache.turbine.services.pull;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import org.apache.commons.configuration.Configuration;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import org.apache.turbine.Turbine;
29  import org.apache.turbine.om.security.User;
30  import org.apache.turbine.services.InitializationException;
31  import org.apache.turbine.services.TurbineBaseService;
32  import org.apache.turbine.services.pool.PoolService;
33  import org.apache.turbine.services.pool.TurbinePool;
34  import org.apache.turbine.services.security.TurbineSecurity;
35  import org.apache.turbine.services.velocity.VelocityService;
36  import org.apache.turbine.services.velocity.TurbineVelocity;
37  import org.apache.turbine.util.RunData;
38  
39  import org.apache.velocity.context.Context;
40  
41  /***
42   * This is the concrete implementation of the Turbine
43   * Pull Service.
44   * <p>
45   * These are tools that are placed in the context by the service
46   * These tools will be made available to all your
47   * templates. You list the tools in the following way:
48   * <p>
49   * <pre>
50   * tool.&lt;scope&gt;.&lt;id&gt; = &lt;classname&gt;
51   *
52   * &lt;scope&gt;      is the tool scope: global, request, session,
53   *              authorized or persistent (see below for more details)
54   * &lt;id&gt;         is the name of the tool in the context
55   *
56   * You can configure the tools in this way:
57   * tool.&lt;id&gt;.&lt;parameter&gt; = &lt;value&gt;
58   *
59   * So if you find "global", "request", "session" or "persistent" as second
60   * part, it is a configuration to put a tool into the toolbox, else it is a
61   * tool specific configuration.
62   *
63   * For example:
64   *
65   * tool.global.ui    = org.apache.turbine.util.pull.UIManager
66   * tool.global.mm    = org.apache.turbine.util.pull.MessageManager
67   * tool.request.link = org.apache.turbine.services.pull.tools.TemplateLink
68   * tool.request.page = org.apache.turbine.util.template.TemplatePageAttributes
69   *
70   * Then:
71   *
72   * tool.ui.skin = default
73   *
74   * configures the value of "skin" for the "ui" tool.
75   *
76   * Tools are accessible in all templates by the <id> given
77   * to the tool. So for the above listings the UIManager would
78   * be available as $ui, the MessageManager as $mm, the TemplateLink
79   * as $link and the TemplatePageAttributes as $page.
80   *
81   * You should avoid using tool names called "global", "request",
82   * "session" or "persistent" because of clashes with the possible Scopes.
83   *
84   * Scopes:
85   *
86   *  global:     tool is instantiated once and that instance is available
87   *              to all templates for all requests. Tool must be threadsafe.
88   *
89   *  request:    tool is instantiated once for each request (although the
90   *              PoolService is used to recycle instances). Tool need not
91   *              be threadsafe.
92   *
93   *  session:    tool is instantiated once for each user session, and is
94   *              stored in the session.  These tools do not need to be
95   *              threadsafe.
96   *
97   *  authorized: tool is instantiated once for each user session once the
98   *              user logs in. After this, it is a normal session tool.
99   *
100  *  persistent: tool is instantitated once for each user session once
101  *              the user logs in and is is stored in the user's permanent
102  *              hashtable.
103  *              This means for a logged in user the tool will be persisted
104  *              in the user's objectdata. Tool should be Serializable.  These
105  *              tools do not need to be threadsafe.
106  *              <b>persistent scope tools are deprecated in 2.3</b>
107  *
108  * Defaults: none
109  * </pre>
110  *
111  * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
112  * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
113  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
114  * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
115  * @version $Id: TurbinePullService.java 264148 2005-08-29 14:21:04Z henning $
116  */
117 public class TurbinePullService
118         extends TurbineBaseService
119         implements PullService
120 {
121     /*** Logging */
122     private static Log log = LogFactory.getLog(TurbinePullService.class);
123 
124     /*** Reference to the pool service */
125     private PoolService pool = null;
126 
127     /*** Reference to the templating (nee Velocity) service */
128     private VelocityService velocity = null;
129 
130     /***
131      * This is the container for the global web application
132      * tools that are used in conjunction with the
133      * Turbine Pull Model. All the global tools will be placed
134      * in this Context and be made accessible inside
135      * templates via the tool name specified in the TR.props
136      * file.
137      */
138     private Context globalContext;
139 
140     /***
141      * This inner class is used in the lists below to store the
142      * tool name and class for each of request, session and persistent
143      * tools
144      */
145     private static class ToolData
146     {
147         String toolName;
148         String toolClassName;
149         Class toolClass;
150 
151         public ToolData(String toolName, String toolClassName, Class toolClass)
152         {
153             this.toolName = toolName;
154             this.toolClassName = toolClassName;
155             this.toolClass = toolClass;
156         }
157     }
158 
159     /*** Internal list of global tools */
160     private List globalTools;
161 
162     /*** Internal list of request tools */
163     private List requestTools;
164 
165     /*** Internal list of session tools */
166     private List sessionTools;
167 
168     /*** Internal list of authorized tools */
169     private List authorizedTools;
170 
171     /*** Internal list of persistent tools */
172     private List persistentTools;
173 
174     /*** Directory where application tool resources are stored.*/
175     private String resourcesDirectory;
176 
177     /*** Should we refresh the application tools on a per request basis? */
178     private boolean refreshToolsPerRequest = false;
179 
180     /***
181      * Called the first time the Service is used.
182      */
183     public void init()
184         throws InitializationException
185     {
186         try
187         {
188             pool = TurbinePool.getService();
189 
190             if (pool == null)
191             {
192                 throw new InitializationException("Pull Service requires"
193                     + " configured Pool Service!");
194             }
195 
196             initPullService();
197             // Make sure to setInit(true) because Tools may
198             // make calls back to the TurbinePull static methods
199             // which causes an init loop.
200             setInit(true);
201 
202             // Do _NOT_ move this before the setInit(true)
203             velocity = TurbineVelocity.getService();
204 
205             if (velocity != null)
206             {
207                 initPullTools();
208             }
209             else
210             {
211                 log.info("Velocity Service not configured, skipping pull tools!");
212             }
213         }
214         catch (Exception e)
215         {
216             throw new InitializationException(
217                 "TurbinePullService failed to initialize", e);
218         }
219     }
220 
221     /***
222      * Initialize the pull service
223      *
224      * @exception Exception A problem happened when starting up
225      */
226     private void initPullService()
227         throws Exception
228     {
229         // This is the per-service configuration, prefixed with services.PullService
230         Configuration conf = getConfiguration();
231 
232         // Get the resources directory that is specificed
233         // in the TR.props or default to "resources", relative to the webapp.
234         resourcesDirectory = conf.getString(
235             TOOL_RESOURCES_DIR_KEY,
236             TOOL_RESOURCES_DIR_DEFAULT);
237 
238         // Should we refresh the tool box on a per
239         // request basis.
240         refreshToolsPerRequest =
241             conf.getBoolean(
242                 TOOLS_PER_REQUEST_REFRESH_KEY,
243                 TOOLS_PER_REQUEST_REFRESH_DEFAULT);
244 
245         // Log the fact that the application tool box will
246         // be refreshed on a per request basis.
247         if (refreshToolsPerRequest)
248         {
249             log.info("Pull Model tools will "
250                 + "be refreshed on a per request basis.");
251         }
252     }
253 
254     /***
255      * Initialize the pull tools. At this point, the
256      * service must be marked as initialized, because the
257      * tools may call the methods of this service via the
258      * static facade class TurbinePull.
259      *
260      * @exception Exception A problem happened when starting up
261      */
262     private void initPullTools()
263         throws Exception
264     {
265         // And for reasons I never really fully understood,
266         // the tools directive is toplevel without the service
267         // prefix. This is brain-damaged but for legacy reasons we
268         // keep this. So this is the global turbine configuration:
269         Configuration conf = Turbine.getConfiguration();
270 
271         // Grab each list of tools that are to be used (for global scope,
272         // request scope, authorized scope, session scope and persistent
273         // scope tools). They are specified respectively in the TR.props
274         // like this:
275         //
276         // tool.global.ui = org.apache.turbine.util.pull.UIManager
277         // tool.global.mm = org.apache.turbine.util.pull.MessageManager
278         //
279         // tool.request.link = org.apache.turbine.services.pull.tools.TemplateLink
280         //
281         // tool.session.basket = org.sample.util.ShoppingBasket;
282         //
283         // tool.persistent.ui = org.apache.turbine.services.pull.util.PersistentUIManager
284 
285         log.debug("Global Tools:");
286         globalTools     = getTools(conf.subset(GLOBAL_TOOL));
287         log.debug("Request Tools:");
288         requestTools    = getTools(conf.subset(REQUEST_TOOL));
289         log.debug("Session Tools:");
290         sessionTools    = getTools(conf.subset(SESSION_TOOL));
291         log.debug("Authorized Tools:");
292         authorizedTools = getTools(conf.subset(AUTHORIZED_TOOL));
293         log.debug("Persistent Tools:");
294         persistentTools = getTools(conf.subset(PERSISTENT_TOOL));
295 
296         // Create and populate the global context right now
297 
298         // This is unholy, because it entwines the VelocityService and
299         // the Pull Service even further. However, there isn't much we can
300         // do for the 2.3 release. Expect this to go post-2.3
301         globalContext = velocity.getNewContext();
302 
303         populateWithGlobalTools(globalContext);
304     }
305 
306     /***
307      * Retrieve the tool names and classes for the tools definied
308      * in the configuration file with the prefix given.
309      *
310      * @param toolConfig The part of the configuration describing some tools
311      */
312     private List getTools(Configuration toolConfig)
313     {
314         List tools = new ArrayList();
315 
316         // There might not be any tools for this prefix
317         // so return an empty list.
318         if (toolConfig == null)
319         {
320             return tools;
321         }
322 
323         for (Iterator it = toolConfig.getKeys(); it.hasNext();)
324         {
325             String toolName = (String) it.next();
326             String toolClassName = toolConfig.getString(toolName);
327 
328             try
329             {
330                 // Create an instance of the tool class.
331                 Class toolClass = Class.forName(toolClassName);
332 
333                 // Add the tool to the list being built.
334                 tools.add(new ToolData(toolName, toolClassName, toolClass));
335 
336                 log.info("Tool " + toolClassName
337                     + " to add to the context as '$" + toolName + "'");
338             }
339             catch (Exception e)
340             {
341                 log.error("Cannot instantiate tool class "
342                     + toolClassName + ": ", e);
343             }
344         }
345 
346         return tools;
347     }
348 
349     /***
350      * Return the Context which contains all global tools that
351      * are to be used in conjunction with the Turbine
352      * Pull Model. The tools are refreshed every time the
353      * global Context is pulled.
354      */
355     public Context getGlobalContext()
356     {
357         if (refreshToolsPerRequest)
358         {
359             refreshGlobalTools();
360         }
361         return globalContext;
362     }
363 
364     /***
365      * Populate the given context with all request, session, authorized
366      * and persistent scope tools (it is assumed that the context
367      * already wraps the global context, and thus already contains
368      * the global tools).
369      *
370      * @param context a Velocity Context to populate
371      * @param data a RunData object for request specific data
372      */
373     public void populateContext(Context context, RunData data)
374     {
375         populateWithRequestTools(context, data);
376 
377         // session tools (whether session-only or persistent are
378         // very similar, so the same method is used - the
379         // boolean parameter indicates whether get/setPerm is to be used
380         // rather than get/setTemp)
381 
382         //
383         // Session Tool start right at the session once the user has been set
384         // while persistent and authorized Tools are started when the user has
385         // logged in
386         //
387         User user = data.getUser();
388 
389         // Note: Session tools are currently lost after the login action
390         // because the anonymous user is replaced the the real user object.
391         // We should either store the session pull tools in the session or
392         // make Turbine.loginAction() copy the session pull tools into the
393         // new user object.
394         populateWithSessionTools(sessionTools, context, data, user);
395 
396         if (!TurbineSecurity.isAnonymousUser(user))
397         {
398             if (user.hasLoggedIn())
399             {
400                 populateWithSessionTools(authorizedTools, context, data, user);
401                 populateWithPermTools(persistentTools, context, data, user);
402             }
403         }
404     }
405 
406     /***
407      * Populate the given context with the global tools
408      *
409      * @param context a Velocity Context to populate
410      */
411     private void populateWithGlobalTools(Context context)
412     {
413         for (Iterator it = globalTools.iterator(); it.hasNext();)
414         {
415             ToolData toolData = (ToolData) it.next();
416             try
417             {
418                 Object tool = toolData.toolClass.newInstance();
419 
420                 // global tools are init'd with a null data parameter
421                 initTool(tool, null);
422 
423                 // put the tool in the context
424                 context.put(toolData.toolName, tool);
425             }
426             catch (Exception e)
427             {
428                 log.error("Could not instantiate global tool "
429                     + toolData.toolName + " from a "
430                     + toolData.toolClassName + " object", e);
431             }
432         }
433     }
434 
435     /***
436      * Populate the given context with the request-scope tools
437      *
438      * @param context a Velocity Context to populate
439      * @param data a RunData instance
440      */
441     private void populateWithRequestTools(Context context, RunData data)
442     {
443         // Iterate the tools
444         for (Iterator it = requestTools.iterator(); it.hasNext();)
445         {
446             ToolData toolData = (ToolData) it.next();
447             try
448             {
449                 // Fetch Object through the Pool.
450                 Object tool = pool.getInstance(toolData.toolClass);
451 
452                 // request tools are init'd with a RunData object
453                 initTool(tool, data);
454 
455                 // put the tool in the context
456                 context.put(toolData.toolName, tool);
457             }
458             catch (Exception e)
459             {
460                 log.error("Could not instantiate request tool "
461                     + toolData.toolName + " from a "
462                     + toolData.toolClassName + " object", e);
463             }
464         }
465     }
466 
467     /***
468      * Populate the given context with the session-scoped tools.
469      *
470      * @param tools The list of tools with which to populate the
471      * session.
472      * @param context The context to populate.
473      * @param data The current RunData object
474      * @param user The <code>User</code> object whose storage to
475      * retrieve the tool from.
476      */
477     private void populateWithSessionTools(List tools, Context context,
478             RunData data, User user)
479     {
480         // Iterate the tools
481         for (Iterator it = tools.iterator(); it.hasNext();)
482         {
483             ToolData toolData = (ToolData) it.next();
484             try
485             {
486                 // ensure that tool is created only once for a user
487                 // by synchronizing against the user object
488                 synchronized (data.getSession())
489                 {
490                     // first try and fetch the tool from the user's
491                     // hashtable
492                     Object tool = data.getSession().getAttribute(
493                             SESSION_TOOLS_ATTRIBUTE_PREFIX
494                             + toolData.toolClassName);
495 
496                     if (tool == null)
497                     {
498                         // if not there, an instance must be fetched from
499                         // the pool
500                         tool = pool.getInstance(toolData.toolClass);
501 
502                         // session tools are init'd with the User object
503                         initTool(tool, user);
504 
505                         // store the newly created tool in the session
506                         data.getSession().setAttribute(
507                                 SESSION_TOOLS_ATTRIBUTE_PREFIX
508                                 + tool.getClass().getName(), tool);
509                     }
510 
511                     // *NOT* else
512                     if(tool != null)
513                     {
514                         // This is a semantics change. In the old
515                         // Turbine, Session tools were initialized and
516                         // then refreshed every time they were pulled
517                         // into the context if "refreshToolsPerRequest"
518                         // was wanted.
519                         //
520                         // RunDataApplicationTools now have a parameter
521                         // for refresh. If it is not refreshed immediately
522                         // after init(), the parameter value will be undefined
523                         // until the 2nd run. So we refresh all the session
524                         // tools on every run, even if we just init'ed it.
525                         //
526 
527                         if (refreshToolsPerRequest)
528                         {
529                             refreshTool(tool, data);
530                         }
531 
532                         // put the tool in the context
533                         log.debug("Adding " + tool + " to ctx as "
534                                 + toolData.toolName);
535                         context.put(toolData.toolName, tool);
536                     }
537                     else
538                     {
539                         log.info("Tool " + toolData.toolName
540                                 + " was null, skipping it.");
541                     }
542                 }
543             }
544             catch (Exception e)
545             {
546                 log.error("Could not instantiate session tool "
547                     + toolData.toolName + " from a "
548                     + toolData.toolClassName + " object", e);
549             }
550         }
551     }
552 
553     /***
554      * Populate the given context with the perm-scoped tools.
555      *
556      * @param tools The list of tools with which to populate the
557      * session.
558      * @param context The context to populate.
559      * @param data The current RunData object
560      * @param user The <code>User</code> object whose storage to
561      * retrieve the tool from.
562      */
563     private void populateWithPermTools(List tools, Context context,
564             RunData data, User user)
565     {
566         // Iterate the tools
567         for (Iterator it = tools.iterator(); it.hasNext();)
568         {
569             ToolData toolData = (ToolData) it.next();
570             try
571             {
572                 // ensure that tool is created only once for a user
573                 // by synchronizing against the user object
574                 synchronized (user)
575                 {
576                     // first try and fetch the tool from the user's
577                     // hashtable
578                     Object tool = user.getPerm(toolData.toolClassName);
579 
580                     if (tool == null)
581                     {
582                         // if not there, an instance must be fetched from
583                         // the pool
584                         tool = pool.getInstance(toolData.toolClass);
585 
586                         // session tools are init'd with the User object
587                         initTool(tool, user);
588 
589                         // store the newly created tool in the user's hashtable
590                         user.setPerm(toolData.toolClassName, tool);
591                     }
592 
593                     // *NOT* else
594                     if(tool != null)
595                     {
596                         // This is a semantics change. In the old
597                         // Turbine, Session tools were initialized and
598                         // then refreshed every time they were pulled
599                         // into the context if "refreshToolsPerRequest"
600                         // was wanted.
601                         //
602                         // RunDataApplicationTools now have a parameter
603                         // for refresh. If it is not refreshed immediately
604                         // after init(), the parameter value will be undefined
605                         // until the 2nd run. So we refresh all the session
606                         // tools on every run, even if we just init'ed it.
607                         //
608 
609                         if (refreshToolsPerRequest)
610                         {
611                             refreshTool(tool, data);
612                         }
613 
614                         // put the tool in the context
615                         log.debug("Adding " + tool + " to ctx as "
616                                 + toolData.toolName);
617                         log.warn("Persistent scope tools are deprecated.");
618                         context.put(toolData.toolName, tool);
619                     }
620                     else
621                     {
622                         log.info("Tool " + toolData.toolName
623                                 + " was null, skipping it.");
624                     }
625                 }
626             }
627             catch (Exception e)
628             {
629                 log.error("Could not instantiate perm tool "
630                     + toolData.toolName + " from a "
631                     + toolData.toolClassName + " object", e);
632             }
633         }
634     }
635 
636     /***
637      * Return the absolute path to the resources directory
638      * used by the application tools.
639      *
640      * @return the absolute path of the resources directory
641      */
642     public String getAbsolutePathToResourcesDirectory()
643     {
644         return Turbine.getRealPath(resourcesDirectory);
645     }
646 
647     /***
648      * Return the resources directory. This is
649      * relative to the web context.
650      *
651      * @return the relative path of the resources directory
652      */
653     public String getResourcesDirectory()
654     {
655         return resourcesDirectory;
656     }
657 
658     /***
659      * Refresh the global tools. We can
660      * only refresh those tools that adhere to
661      * ApplicationTool interface because we
662      * know those types of tools have a refresh
663      * method.
664      * @deprecated Will be made private after 2.3
665      */
666     public void refreshGlobalTools()
667     {
668         for (Iterator it = globalTools.iterator(); it.hasNext();)
669         {
670             ToolData toolData = (ToolData) it.next();
671             Object tool = globalContext.get(toolData.toolName);
672             refreshTool(tool, null);
673         }
674     }
675 
676     /***
677      * Should we refresh the ToolBox on
678      * a per request basis.
679      * @deprecated No longer needed as Pull and Velocity Service are now more separate.
680      */
681     public boolean refreshToolsPerRequest()
682     {
683         return refreshToolsPerRequest;
684     }
685 
686     /***
687      * Release the request-scope tool instances in the
688      * given Context back to the pool
689      *
690      * @param context the Velocity Context to release tools from
691      */
692     public void releaseTools(Context context)
693     {
694         // only the request tools can be released - other scoped
695         // tools will have continuing references to them
696         releaseTools(context, requestTools);
697     }
698 
699     /***
700      * Release the given list of tools from the context back
701      * to the pool
702      *
703      * @param context the Context containing the tools
704      * @param tools a List of ToolData objects
705      */
706     private void releaseTools(Context context, List tools)
707     {
708         for (Iterator it = tools.iterator(); it.hasNext();)
709         {
710             ToolData toolData = (ToolData) it.next();
711             Object tool = context.remove(toolData.toolName);
712 
713             if (tool != null)
714             {
715                 pool.putInstance(tool);
716             }
717         }
718     }
719 
720     /***
721      * Initialized a given Tool with the passed init Object
722      *
723      * @param tool A Tool Object
724      * @param param The Init Parameter
725      *
726      * @throws Exception If anything went wrong.
727      */
728     private void initTool(Object tool, Object param)
729         throws Exception
730     {
731         if (tool instanceof ApplicationTool)
732         {
733             ((ApplicationTool) tool).init(param);
734         }
735         else if (tool instanceof RunDataApplicationTool)
736         {
737             ((RunDataApplicationTool) tool).init(param);
738         }
739     }
740 
741     /***
742      * Refresh a given Tool.
743      *
744      * @param tool A Tool Object
745      * @param data The current RunData Object
746      */
747     private void refreshTool(Object tool, RunData data)
748     {
749         if (tool instanceof ApplicationTool)
750         {
751             ((ApplicationTool) tool).refresh();
752         }
753         else if (tool instanceof RunDataApplicationTool)
754         {
755             ((RunDataApplicationTool) tool).refresh(data);
756         }
757     }
758 }