001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.web.struts;
009    
010    import java.util.HashMap;
011    import java.util.Map;
012    
013    import javax.servlet.http.HttpServletRequest;
014    
015    import org.apache.struts.action.Action;
016    import org.apache.struts.action.ActionMapping;
017    import org.apache.struts.action.ActionServlet;
018    import org.picocontainer.MutablePicoContainer;
019    import org.picocontainer.PicoCompositionException;
020    import org.picocontainer.web.PicoServletContainerFilter;
021    
022    /**
023     * Uses PicoContainer to produce Actions and inject dependencies into them. If
024     * you have your own <code>RequestProcessor</code> implementation, you can use
025     * an <code>ActionFactory</code> in your
026     * <code>RequestProcessor.processActionCreate</code> method to Picofy your
027     * Actions.
028     * 
029     * @author Stephen Molitor
030     * @author Mauro Talevi
031     */
032    public final class PicoActionFactory {
033    
034        private static ThreadLocal<MutablePicoContainer> currentRequestContainer = new ThreadLocal<MutablePicoContainer>();
035        private static ThreadLocal<MutablePicoContainer> currentSessionContainer = new ThreadLocal<MutablePicoContainer>();
036        private static ThreadLocal<MutablePicoContainer> currentAppContainer = new ThreadLocal<MutablePicoContainer>();
037    
038        @SuppressWarnings("serial")
039        public static class ServletFilter extends PicoServletContainerFilter {
040            protected void setAppContainer(MutablePicoContainer container) {
041                currentAppContainer.set(container);
042            }
043            protected void setRequestContainer(MutablePicoContainer container) {
044                currentRequestContainer.set(container);
045            }
046            protected void setSessionContainer(MutablePicoContainer container) {
047                currentSessionContainer.set(container);
048            }
049        }
050    
051        private final Map<String, Class<?>> classCache = new HashMap<String, Class<?>>();
052    
053        /**
054         * Gets the <code>Action</code> specified by the mapping type from a
055         * PicoContainer. The action will be instantiated if necessary, and its
056         * dependencies will be injected. The action will be instantiated via a
057         * special PicoContainer that just contains actions. If this container
058         * already exists in the request attribute, this method will use it. If no
059         * such container exists, this method will create a new Pico container and
060         * place it in the request. The parent container will either be the request
061         * container, or if that container can not be found the session container,
062         * or if that container can not be found, the application container. If no
063         * parent container can be found, a <code>PicoCompositionException</code>
064         * will be thrown. The action path specified in the mapping is used as the
065         * component key for the action.
066         * 
067         * @param request the Http servlet request.
068         * @param mapping the Struts mapping object, whose type property tells us
069         *            what Action class is required.
070         * @param servlet the Struts <code>ActionServlet</code>.
071         * @return the <code>Action</code> instance.
072         * @throws PicoCompositionException if the mapping type does not specify a
073         *             valid action.
074         * @throws PicoCompositionException if no request, session, or application
075         *             scoped Pico container can be found.
076         */
077        public Action getAction(HttpServletRequest request, ActionMapping mapping, ActionServlet servlet)
078                throws PicoCompositionException {
079    
080            MutablePicoContainer actionsContainer = currentRequestContainer.get();
081            Object actionKey = mapping.getPath();
082            Class<?> actionType = getActionClass(mapping.getType());
083    
084            Action action = (Action) actionsContainer.getComponent(actionKey);
085            if (action == null) {
086                actionsContainer.addComponent(actionKey, actionType);
087                action = (Action) actionsContainer.getComponent(actionKey);
088            }
089    
090            action.setServlet(servlet);
091            return action;
092        }
093    
094        public Class<?> getActionClass(String className) throws PicoCompositionException {
095            try {
096                return loadClass(className);
097            } catch (ClassNotFoundException e) {
098                throw new PicoCompositionException("Action class '" + className + "' not found", e);
099            }
100        }
101    
102        protected Class<?> loadClass(String className) throws ClassNotFoundException {
103            if (classCache.containsKey(className)) {
104                return (Class<?>) classCache.get(className);
105            } else {
106                Class<?> result = Thread.currentThread().getContextClassLoader().loadClass(className);
107                classCache.put(className, result);
108                return result;
109            }
110        }
111    
112    }