001: package org.apache.turbine.services.pull;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import org.apache.commons.configuration.Configuration;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import org.apache.turbine.Turbine;
032: import org.apache.turbine.om.security.User;
033: import org.apache.turbine.services.InitializationException;
034: import org.apache.turbine.services.TurbineBaseService;
035: import org.apache.turbine.services.pool.PoolService;
036: import org.apache.turbine.services.pool.TurbinePool;
037: import org.apache.turbine.services.security.TurbineSecurity;
038: import org.apache.turbine.services.velocity.VelocityService;
039: import org.apache.turbine.services.velocity.TurbineVelocity;
040: import org.apache.turbine.util.RunData;
041:
042: import org.apache.velocity.context.Context;
043:
044: /**
045: * This is the concrete implementation of the Turbine
046: * Pull Service.
047: * <p>
048: * These are tools that are placed in the context by the service
049: * These tools will be made available to all your
050: * templates. You list the tools in the following way:
051: * <p>
052: * <pre>
053: * tool.<scope>.<id> = <classname>
054: *
055: * <scope> is the tool scope: global, request, session,
056: * authorized or persistent (see below for more details)
057: * <id> is the name of the tool in the context
058: *
059: * You can configure the tools in this way:
060: * tool.<id>.<parameter> = <value>
061: *
062: * So if you find "global", "request", "session" or "persistent" as second
063: * part, it is a configuration to put a tool into the toolbox, else it is a
064: * tool specific configuration.
065: *
066: * For example:
067: *
068: * tool.global.ui = org.apache.turbine.util.pull.UIManager
069: * tool.global.mm = org.apache.turbine.util.pull.MessageManager
070: * tool.request.link = org.apache.turbine.services.pull.tools.TemplateLink
071: * tool.request.page = org.apache.turbine.util.template.HtmlPageAttributes
072: *
073: * Then:
074: *
075: * tool.ui.skin = default
076: *
077: * configures the value of "skin" for the "ui" tool.
078: *
079: * Tools are accessible in all templates by the <id> given
080: * to the tool. So for the above listings the UIManager would
081: * be available as $ui, the MessageManager as $mm, the TemplateLink
082: * as $link and the HtmlPageAttributes as $page.
083: *
084: * You should avoid using tool names called "global", "request",
085: * "session" or "persistent" because of clashes with the possible Scopes.
086: *
087: * Scopes:
088: *
089: * global: tool is instantiated once and that instance is available
090: * to all templates for all requests. Tool must be threadsafe.
091: *
092: * request: tool is instantiated once for each request (although the
093: * PoolService is used to recycle instances). Tool need not
094: * be threadsafe.
095: *
096: * session: tool is instantiated once for each user session, and is
097: * stored in the session. These tools do not need to be
098: * threadsafe.
099: *
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 extends TurbineBaseService implements
121: PullService {
122: /** Logging */
123: private static Log log = LogFactory
124: .getLog(TurbinePullService.class);
125:
126: /** Reference to the pool service */
127: private PoolService pool = null;
128:
129: /** Reference to the templating (nee Velocity) service */
130: private VelocityService velocity = null;
131:
132: /**
133: * This is the container for the global web application
134: * tools that are used in conjunction with the
135: * Turbine Pull Model. All the global tools will be placed
136: * in this Context and be made accessible inside
137: * templates via the tool name specified in the TR.props
138: * file.
139: */
140: private Context globalContext;
141:
142: /**
143: * This inner class is used in the lists below to store the
144: * tool name and class for each of request, session and persistent
145: * tools
146: */
147: private static class ToolData {
148: String toolName;
149: String toolClassName;
150: Class toolClass;
151:
152: public ToolData(String toolName, String toolClassName,
153: Class toolClass) {
154: this .toolName = toolName;
155: this .toolClassName = toolClassName;
156: this .toolClass = toolClass;
157: }
158: }
159:
160: /** Internal list of global tools */
161: private List globalTools;
162:
163: /** Internal list of request tools */
164: private List requestTools;
165:
166: /** Internal list of session tools */
167: private List sessionTools;
168:
169: /** Internal list of authorized tools */
170: private List authorizedTools;
171:
172: /** Internal list of persistent tools */
173: private List persistentTools;
174:
175: /** Directory where application tool resources are stored.*/
176: private String resourcesDirectory;
177:
178: /** Should we refresh the application tools on a per request basis? */
179: private boolean refreshToolsPerRequest = false;
180:
181: /**
182: * Called the first time the Service is used.
183: */
184: public void init() throws InitializationException {
185: try {
186: pool = TurbinePool.getService();
187:
188: if (pool == null) {
189: throw new InitializationException(
190: "Pull Service requires"
191: + " configured Pool Service!");
192: }
193:
194: initPullService();
195: // Make sure to setInit(true) because Tools may
196: // make calls back to the TurbinePull static methods
197: // which causes an init loop.
198: setInit(true);
199:
200: // Do _NOT_ move this before the setInit(true)
201: velocity = TurbineVelocity.getService();
202:
203: if (velocity != null) {
204: initPullTools();
205: } else {
206: log
207: .info("Velocity Service not configured, skipping pull tools!");
208: }
209: } catch (Exception e) {
210: throw new InitializationException(
211: "TurbinePullService failed to initialize", e);
212: }
213: }
214:
215: /**
216: * Initialize the pull service
217: *
218: * @exception Exception A problem happened when starting up
219: */
220: private void initPullService() throws Exception {
221: // This is the per-service configuration, prefixed with services.PullService
222: Configuration conf = getConfiguration();
223:
224: // Get the resources directory that is specificed
225: // in the TR.props or default to "resources", relative to the webapp.
226: resourcesDirectory = conf.getString(TOOL_RESOURCES_DIR_KEY,
227: TOOL_RESOURCES_DIR_DEFAULT);
228:
229: // Should we refresh the tool box on a per
230: // request basis.
231: refreshToolsPerRequest = conf.getBoolean(
232: TOOLS_PER_REQUEST_REFRESH_KEY,
233: TOOLS_PER_REQUEST_REFRESH_DEFAULT);
234:
235: // Log the fact that the application tool box will
236: // be refreshed on a per request basis.
237: if (refreshToolsPerRequest) {
238: log.info("Pull Model tools will "
239: + "be refreshed on a per request basis.");
240: }
241: }
242:
243: /**
244: * Initialize the pull tools. At this point, the
245: * service must be marked as initialized, because the
246: * tools may call the methods of this service via the
247: * static facade class TurbinePull.
248: *
249: * @exception Exception A problem happened when starting up
250: */
251: private void initPullTools() throws Exception {
252: // And for reasons I never really fully understood,
253: // the tools directive is toplevel without the service
254: // prefix. This is brain-damaged but for legacy reasons we
255: // keep this. So this is the global turbine configuration:
256: Configuration conf = Turbine.getConfiguration();
257:
258: // Grab each list of tools that are to be used (for global scope,
259: // request scope, authorized scope, session scope and persistent
260: // scope tools). They are specified respectively in the TR.props
261: // like this:
262: //
263: // tool.global.ui = org.apache.turbine.util.pull.UIManager
264: // tool.global.mm = org.apache.turbine.util.pull.MessageManager
265: //
266: // tool.request.link = org.apache.turbine.services.pull.tools.TemplateLink
267: //
268: // tool.session.basket = org.sample.util.ShoppingBasket;
269: //
270: // tool.persistent.ui = org.apache.turbine.services.pull.util.PersistentUIManager
271:
272: log.debug("Global Tools:");
273: globalTools = getTools(conf.subset(GLOBAL_TOOL));
274: log.debug("Request Tools:");
275: requestTools = getTools(conf.subset(REQUEST_TOOL));
276: log.debug("Session Tools:");
277: sessionTools = getTools(conf.subset(SESSION_TOOL));
278: log.debug("Authorized Tools:");
279: authorizedTools = getTools(conf.subset(AUTHORIZED_TOOL));
280: log.debug("Persistent Tools:");
281: persistentTools = getTools(conf.subset(PERSISTENT_TOOL));
282:
283: // Create and populate the global context right now
284:
285: // This is unholy, because it entwines the VelocityService and
286: // the Pull Service even further. However, there isn't much we can
287: // do for the 2.3 release. Expect this to go post-2.3
288: globalContext = velocity.getNewContext();
289:
290: populateWithGlobalTools(globalContext);
291: }
292:
293: /**
294: * Retrieve the tool names and classes for the tools definied
295: * in the configuration file with the prefix given.
296: *
297: * @param toolConfig The part of the configuration describing some tools
298: */
299: private List getTools(Configuration toolConfig) {
300: List tools = new ArrayList();
301:
302: // There might not be any tools for this prefix
303: // so return an empty list.
304: if (toolConfig == null) {
305: return tools;
306: }
307:
308: for (Iterator it = toolConfig.getKeys(); it.hasNext();) {
309: String toolName = (String) it.next();
310: String toolClassName = toolConfig.getString(toolName);
311:
312: try {
313: // Create an instance of the tool class.
314: Class toolClass = Class.forName(toolClassName);
315:
316: // Add the tool to the list being built.
317: tools.add(new ToolData(toolName, toolClassName,
318: toolClass));
319:
320: log.info("Tool " + toolClassName
321: + " to add to the context as '$" + toolName
322: + "'");
323: } catch (Exception e) {
324: log.error("Cannot instantiate tool class "
325: + toolClassName + ": ", e);
326: }
327: }
328:
329: return tools;
330: }
331:
332: /**
333: * Return the Context which contains all global tools that
334: * are to be used in conjunction with the Turbine
335: * Pull Model. The tools are refreshed every time the
336: * global Context is pulled.
337: */
338: public Context getGlobalContext() {
339: if (refreshToolsPerRequest) {
340: refreshGlobalTools();
341: }
342: return globalContext;
343: }
344:
345: /**
346: * Populate the given context with all request, session, authorized
347: * and persistent scope tools (it is assumed that the context
348: * already wraps the global context, and thus already contains
349: * the global tools).
350: *
351: * @param context a Velocity Context to populate
352: * @param data a RunData object for request specific data
353: */
354: public void populateContext(Context context, RunData data) {
355: populateWithRequestTools(context, data);
356:
357: // session tools (whether session-only or persistent are
358: // very similar, so the same method is used - the
359: // boolean parameter indicates whether get/setPerm is to be used
360: // rather than get/setTemp)
361:
362: //
363: // Session Tool start right at the session once the user has been set
364: // while persistent and authorized Tools are started when the user has
365: // logged in
366: //
367: User user = data.getUser();
368:
369: // Note: Session tools are currently lost after the login action
370: // because the anonymous user is replaced the the real user object.
371: // We should either store the session pull tools in the session or
372: // make Turbine.loginAction() copy the session pull tools into the
373: // new user object.
374: populateWithSessionTools(sessionTools, context, data, user);
375:
376: if (!TurbineSecurity.isAnonymousUser(user)) {
377: if (user.hasLoggedIn()) {
378: populateWithSessionTools(authorizedTools, context,
379: data, user);
380: populateWithPermTools(persistentTools, context, data,
381: user);
382: }
383: }
384: }
385:
386: /**
387: * Populate the given context with the global tools
388: *
389: * @param context a Velocity Context to populate
390: */
391: private void populateWithGlobalTools(Context context) {
392: for (Iterator it = globalTools.iterator(); it.hasNext();) {
393: ToolData toolData = (ToolData) it.next();
394: try {
395: Object tool = toolData.toolClass.newInstance();
396:
397: // global tools are init'd with a null data parameter
398: initTool(tool, null);
399:
400: // put the tool in the context
401: context.put(toolData.toolName, tool);
402: } catch (Exception e) {
403: log.error("Could not instantiate global tool "
404: + toolData.toolName + " from a "
405: + toolData.toolClassName + " object", e);
406: }
407: }
408: }
409:
410: /**
411: * Populate the given context with the request-scope tools
412: *
413: * @param context a Velocity Context to populate
414: * @param data a RunData instance
415: */
416: private void populateWithRequestTools(Context context, RunData data) {
417: // Iterate the tools
418: for (Iterator it = requestTools.iterator(); it.hasNext();) {
419: ToolData toolData = (ToolData) it.next();
420: try {
421: // Fetch Object through the Pool.
422: Object tool = pool.getInstance(toolData.toolClass);
423:
424: // request tools are init'd with a RunData object
425: initTool(tool, data);
426:
427: // put the tool in the context
428: context.put(toolData.toolName, tool);
429: } catch (Exception e) {
430: log.error("Could not instantiate request tool "
431: + toolData.toolName + " from a "
432: + toolData.toolClassName + " object", e);
433: }
434: }
435: }
436:
437: /**
438: * Populate the given context with the session-scoped tools.
439: *
440: * @param tools The list of tools with which to populate the
441: * session.
442: * @param context The context to populate.
443: * @param data The current RunData object
444: * @param user The <code>User</code> object whose storage to
445: * retrieve the tool from.
446: */
447: private void populateWithSessionTools(List tools, Context context,
448: RunData data, User user) {
449: // Iterate the tools
450: for (Iterator it = tools.iterator(); it.hasNext();) {
451: ToolData toolData = (ToolData) it.next();
452: try {
453: // ensure that tool is created only once for a user
454: // by synchronizing against the user object
455: synchronized (data.getSession()) {
456: // first try and fetch the tool from the user's
457: // hashtable
458: Object tool = data.getSession().getAttribute(
459: SESSION_TOOLS_ATTRIBUTE_PREFIX
460: + toolData.toolClassName);
461:
462: if (tool == null) {
463: // if not there, an instance must be fetched from
464: // the pool
465: tool = pool.getInstance(toolData.toolClass);
466:
467: // session tools are init'd with the User object
468: initTool(tool, user);
469:
470: // store the newly created tool in the session
471: data.getSession().setAttribute(
472: SESSION_TOOLS_ATTRIBUTE_PREFIX
473: + tool.getClass().getName(),
474: tool);
475: }
476:
477: // *NOT* else
478: if (tool != null) {
479: // This is a semantics change. In the old
480: // Turbine, Session tools were initialized and
481: // then refreshed every time they were pulled
482: // into the context if "refreshToolsPerRequest"
483: // was wanted.
484: //
485: // RunDataApplicationTools now have a parameter
486: // for refresh. If it is not refreshed immediately
487: // after init(), the parameter value will be undefined
488: // until the 2nd run. So we refresh all the session
489: // tools on every run, even if we just init'ed it.
490: //
491:
492: if (refreshToolsPerRequest) {
493: refreshTool(tool, data);
494: }
495:
496: // put the tool in the context
497: log.debug("Adding " + tool + " to ctx as "
498: + toolData.toolName);
499: context.put(toolData.toolName, tool);
500: } else {
501: log.info("Tool " + toolData.toolName
502: + " was null, skipping it.");
503: }
504: }
505: } catch (Exception e) {
506: log.error("Could not instantiate session tool "
507: + toolData.toolName + " from a "
508: + toolData.toolClassName + " object", e);
509: }
510: }
511: }
512:
513: /**
514: * Populate the given context with the perm-scoped tools.
515: *
516: * @param tools The list of tools with which to populate the
517: * session.
518: * @param context The context to populate.
519: * @param data The current RunData object
520: * @param user The <code>User</code> object whose storage to
521: * retrieve the tool from.
522: */
523: private void populateWithPermTools(List tools, Context context,
524: RunData data, User user) {
525: // Iterate the tools
526: for (Iterator it = tools.iterator(); it.hasNext();) {
527: ToolData toolData = (ToolData) it.next();
528: try {
529: // ensure that tool is created only once for a user
530: // by synchronizing against the user object
531: synchronized (user) {
532: // first try and fetch the tool from the user's
533: // hashtable
534: Object tool = user.getPerm(toolData.toolClassName);
535:
536: if (tool == null) {
537: // if not there, an instance must be fetched from
538: // the pool
539: tool = pool.getInstance(toolData.toolClass);
540:
541: // session tools are init'd with the User object
542: initTool(tool, user);
543:
544: // store the newly created tool in the user's hashtable
545: user.setPerm(toolData.toolClassName, tool);
546: }
547:
548: // *NOT* else
549: if (tool != null) {
550: // This is a semantics change. In the old
551: // Turbine, Session tools were initialized and
552: // then refreshed every time they were pulled
553: // into the context if "refreshToolsPerRequest"
554: // was wanted.
555: //
556: // RunDataApplicationTools now have a parameter
557: // for refresh. If it is not refreshed immediately
558: // after init(), the parameter value will be undefined
559: // until the 2nd run. So we refresh all the session
560: // tools on every run, even if we just init'ed it.
561: //
562:
563: if (refreshToolsPerRequest) {
564: refreshTool(tool, data);
565: }
566:
567: // put the tool in the context
568: log.debug("Adding " + tool + " to ctx as "
569: + toolData.toolName);
570: log
571: .warn("Persistent scope tools are deprecated.");
572: context.put(toolData.toolName, tool);
573: } else {
574: log.info("Tool " + toolData.toolName
575: + " was null, skipping it.");
576: }
577: }
578: } catch (Exception e) {
579: log.error("Could not instantiate perm tool "
580: + toolData.toolName + " from a "
581: + toolData.toolClassName + " object", e);
582: }
583: }
584: }
585:
586: /**
587: * Return the absolute path to the resources directory
588: * used by the application tools.
589: *
590: * @return the absolute path of the resources directory
591: */
592: public String getAbsolutePathToResourcesDirectory() {
593: return Turbine.getRealPath(resourcesDirectory);
594: }
595:
596: /**
597: * Return the resources directory. This is
598: * relative to the web context.
599: *
600: * @return the relative path of the resources directory
601: */
602: public String getResourcesDirectory() {
603: return resourcesDirectory;
604: }
605:
606: /**
607: * Refresh the global tools. We can
608: * only refresh those tools that adhere to
609: * ApplicationTool interface because we
610: * know those types of tools have a refresh
611: * method.
612: * @deprecated Will be made private after 2.3
613: */
614: public void refreshGlobalTools() {
615: for (Iterator it = globalTools.iterator(); it.hasNext();) {
616: ToolData toolData = (ToolData) it.next();
617: Object tool = globalContext.get(toolData.toolName);
618: refreshTool(tool, null);
619: }
620: }
621:
622: /**
623: * Should we refresh the ToolBox on
624: * a per request basis.
625: * @deprecated No longer needed as Pull and Velocity Service are now more separate.
626: */
627: public boolean refreshToolsPerRequest() {
628: return refreshToolsPerRequest;
629: }
630:
631: /**
632: * Release the request-scope tool instances in the
633: * given Context back to the pool
634: *
635: * @param context the Velocity Context to release tools from
636: */
637: public void releaseTools(Context context) {
638: // only the request tools can be released - other scoped
639: // tools will have continuing references to them
640: releaseTools(context, requestTools);
641: }
642:
643: /**
644: * Release the given list of tools from the context back
645: * to the pool
646: *
647: * @param context the Context containing the tools
648: * @param tools a List of ToolData objects
649: */
650: private void releaseTools(Context context, List tools) {
651: for (Iterator it = tools.iterator(); it.hasNext();) {
652: ToolData toolData = (ToolData) it.next();
653: Object tool = context.remove(toolData.toolName);
654:
655: if (tool != null) {
656: pool.putInstance(tool);
657: }
658: }
659: }
660:
661: /**
662: * Initialized a given Tool with the passed init Object
663: *
664: * @param tool A Tool Object
665: * @param param The Init Parameter
666: *
667: * @throws Exception If anything went wrong.
668: */
669: private void initTool(Object tool, Object param) throws Exception {
670: if (tool instanceof ApplicationTool) {
671: ((ApplicationTool) tool).init(param);
672: } else if (tool instanceof RunDataApplicationTool) {
673: ((RunDataApplicationTool) tool).init(param);
674: }
675: }
676:
677: /**
678: * Refresh a given Tool.
679: *
680: * @param tool A Tool Object
681: * @param data The current RunData Object
682: */
683: private void refreshTool(Object tool, RunData data) {
684: if (tool instanceof ApplicationTool) {
685: ((ApplicationTool) tool).refresh();
686: } else if (tool instanceof RunDataApplicationTool) {
687: ((RunDataApplicationTool) tool).refresh(data);
688: }
689: }
690: }
|