View Javadoc

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