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        @SuppressWarnings("serial")
035        public static class ServletFilter extends PicoServletContainerFilter {
036    
037            private static ThreadLocal<MutablePicoContainer> currentRequestContainer = new ThreadLocal<MutablePicoContainer>();
038            private static ThreadLocal<MutablePicoContainer> currentSessionContainer = new ThreadLocal<MutablePicoContainer>();
039            private static ThreadLocal<MutablePicoContainer> currentAppContainer = new ThreadLocal<MutablePicoContainer>();
040    
041            protected void setAppContainer(MutablePicoContainer container) {
042                currentAppContainer.set(container);
043            }
044    
045            protected void setRequestContainer(MutablePicoContainer container) {
046                currentRequestContainer.set(container);
047            }
048    
049            protected void setSessionContainer(MutablePicoContainer container) {
050                currentSessionContainer.set(container);
051            }
052    
053            private static MutablePicoContainer getRequestContainerForThread() {
054                return currentRequestContainer.get();
055            }
056        }
057    
058        private final Map<String, Class<?>> classCache = new HashMap<String, Class<?>>();
059    
060        /**
061         * Gets the <code>Action</code> specified by the mapping type from a
062         * PicoContainer. The action will be instantiated if necessary, and its
063         * dependencies will be injected. The action will be instantiated via a
064         * special PicoContainer that just contains actions. If this container
065         * already exists in the request attribute, this method will use it. If no
066         * such container exists, this method will create a new Pico container and
067         * place it in the request. The parent container will either be the request
068         * container, or if that container can not be found the session container,
069         * or if that container can not be found, the application container. If no
070         * parent container can be found, a <code>PicoCompositionException</code>
071         * will be thrown. The action path specified in the mapping is used as the
072         * component key for the action.
073         * 
074         * @param request the Http servlet request.
075         * @param mapping the Struts mapping object, whose type property tells us
076         *            what Action class is required.
077         * @param servlet the Struts <code>ActionServlet</code>.
078         * @return the <code>Action</code> instance.
079         * @throws PicoCompositionException if the mapping type does not specify a
080         *             valid action.
081         * @throws PicoCompositionException if no request, session, or application
082         *             scoped Pico container can be found.
083         */
084        public Action getAction(HttpServletRequest request, ActionMapping mapping, ActionServlet servlet)
085                throws PicoCompositionException {
086    
087            MutablePicoContainer actionsContainer = ServletFilter.getRequestContainerForThread();
088            Object actionKey = mapping.getPath();
089            Class<?> actionType = getActionClass(mapping.getType());
090    
091            Action action = (Action) actionsContainer.getComponent(actionKey);
092            if (action == null) {
093                actionsContainer.addComponent(actionKey, actionType);
094                action = (Action) actionsContainer.getComponent(actionKey);
095            }
096    
097            action.setServlet(servlet);
098            return action;
099        }
100    
101        public Class<?> getActionClass(String className) throws PicoCompositionException {
102            try {
103                return loadClass(className);
104            } catch (ClassNotFoundException e) {
105                throw new PicoCompositionException("Action class '" + className + "' not found", e);
106            }
107        }
108    
109        protected Class<?> loadClass(String className) throws ClassNotFoundException {
110            if (classCache.containsKey(className)) {
111                return (Class<?>) classCache.get(className);
112            } else {
113                Class<?> result = Thread.currentThread().getContextClassLoader().loadClass(className);
114                classCache.put(className, result);
115                return result;
116            }
117        }
118    
119    }