001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.gravity;
022
023import java.lang.reflect.Field;
024
025import javax.servlet.ServletConfig;
026import javax.servlet.ServletContext;
027import javax.servlet.ServletException;
028import javax.servlet.http.HttpServlet;
029
030import org.granite.config.ConfigProvider;
031import org.granite.config.GraniteConfig;
032import org.granite.config.GraniteConfigListener;
033import org.granite.config.ServletGraniteConfig;
034import org.granite.config.flex.ServicesConfig;
035import org.granite.config.flex.ServletServicesConfig;
036import org.granite.gravity.config.AbstractActiveMQTopicDestination;
037import org.granite.gravity.config.AbstractJmsTopicDestination;
038import org.granite.gravity.config.AbstractMessagingDestination;
039import org.granite.gravity.config.servlet3.ActiveMQTopicDestination;
040import org.granite.gravity.config.servlet3.JmsTopicDestination;
041import org.granite.gravity.config.servlet3.MessagingDestination;
042import org.granite.gravity.security.GravityDestinationSecurizer;
043import org.granite.util.TypeUtil;
044
045/**
046 * @author Franck WOLFF
047 */
048public class GravityManager {
049
050        private static final String GRAVITY_KEY = Gravity.class.getName();
051        
052        /**
053         * Parse gravity configuration (granite-config.xml), start gravity by using the specified factory and put it
054         * in ServletContext. If Gravity is already started, returns the previous instance from the servlet context.
055         * <br><br>
056         * This method is intended to be used in {@link HttpServlet#init(ServletConfig)} methods only and
057         * synchronizes on the current ServletContext instance.
058         * 
059         * @param servletConfig the servlet config passed in HttpServlet.init(ServletConfig config) method.
060         * @param channelFactory an implementation specific ChannelFactory instance.
061         * @return a newly created and started Gravity instance or previously started one.
062         * @throws ServletException if something goes wrong (GravityFactory not found, Gravity.start() error, etc.)
063         */
064    public static Gravity start(ServletConfig servletConfig) throws ServletException {
065        return start(servletConfig.getServletContext());
066    }
067    
068    public static Gravity start(ServletContext context) throws ServletException {
069        Gravity gravity = null;
070        
071        synchronized (context) {
072                
073                gravity = (Gravity)context.getAttribute(GRAVITY_KEY);
074                
075                if (gravity == null) {
076                        GraniteConfig graniteConfig = ServletGraniteConfig.loadConfig(context);
077                        ServicesConfig servicesConfig = ServletServicesConfig.loadConfig(context);
078                    
079                    Class<?> serverFilterClass = (Class<?>)context.getAttribute(GraniteConfigListener.GRANITE_CONFIG_ATTRIBUTE);
080                    if (serverFilterClass != null)
081                        configureServices(context, serverFilterClass);
082                    
083                        GravityConfig gravityConfig = new GravityConfig(graniteConfig);
084                        
085                        String gravityFactory = gravityConfig.getGravityFactory();
086                        try {
087                                        GravityFactory factory = TypeUtil.newInstance(gravityFactory, GravityFactory.class);
088                                        gravity = factory.newGravity(gravityConfig, servicesConfig, graniteConfig);
089                                } 
090                        catch (Exception e) {
091                                        throw new ServletException("Could not create Gravity instance with factory: " + gravityFactory, e);
092                                }
093                
094                        try {
095                            gravity.start();
096                            context.setAttribute(GRAVITY_KEY, gravity);
097                            GraniteConfigListener.registerShutdownListener(context, gravity);
098                        }
099                        catch (Exception e) {
100                            throw new ServletException("Gravity initialization error", e);
101                        }
102                }
103        }
104
105        return gravity;
106    }
107    
108    /**
109     * Reconfigure gravity with the new supplied configuration (after reloading granite-config.xml).
110     * <br><br>
111     * Only these configuration options are taken into account when reconfiguring Gravity:
112     * <ul>
113     *  <li>channelIdleTimeoutMillis</li>
114     *  <li>longPollingTimeout</li>
115     *  <li>retryOnError</li>
116     *  <li>maxMessagesQueuedPerChannel</li>
117     *  <li>corePoolSize</li>
118     *  <li>maximumPoolSize</li>
119     *  <li>keepAliveTimeMillis</li>
120     * </ul>
121     * 
122     * @param context the ServletContext where the gravity instance is registered.
123     * @param gravityConfig the new (reloaded) GravityConfig. 
124     */
125    public static void reconfigure(ServletContext context, GravityConfig gravityConfig) {
126        synchronized (context) {
127                Gravity gravity = getGravity(context);
128                gravity.reconfigure(gravityConfig, ServletGraniteConfig.getConfig(context));
129        }
130    }
131    
132    /**
133     * Returns a previously started Gravity instance. This method isn't synchronized and should be used in
134     * HttpServlet.doPost(...) methods only.
135     * 
136     * @param context the ServletContext from which to retrieve the Gravity instance. 
137     * @return the unique and started Gravity instance (or null if {@link #start(ServletConfig, ChannelFactory)}
138     *          has never been called).
139     */
140    public static Gravity getGravity(ServletContext context) {
141        return (Gravity)context.getAttribute(GRAVITY_KEY);
142    }
143    
144    
145    private static void configureServices(ServletContext servletContext, Class<?> serverFilterClass) throws ServletException {
146        ServicesConfig servicesConfig = ServletServicesConfig.loadConfig(servletContext);
147        
148        ConfigProvider configProvider = (ConfigProvider)servletContext.getAttribute(GraniteConfigListener.GRANITE_CONFIG_PROVIDER_ATTRIBUTE);
149        
150        for (Field field : serverFilterClass.getDeclaredFields()) {
151                if (field.isAnnotationPresent(MessagingDestination.class)) {
152                        MessagingDestination md = field.getAnnotation(MessagingDestination.class);
153                        AbstractMessagingDestination messagingDestination = new AbstractMessagingDestination();
154                        messagingDestination.setId(field.getName());
155                        messagingDestination.setNoLocal(md.noLocal());
156                        messagingDestination.setSessionSelector(md.sessionSelector());
157                        initSecurizer(messagingDestination, md.securizer(), configProvider);
158                        messagingDestination.initServices(servicesConfig);
159                }
160                else if (field.isAnnotationPresent(JmsTopicDestination.class)) {
161                        JmsTopicDestination md = field.getAnnotation(JmsTopicDestination.class);
162                        AbstractJmsTopicDestination messagingDestination = new AbstractJmsTopicDestination();
163                        messagingDestination.setId(field.getName());
164                        messagingDestination.setNoLocal(md.noLocal());
165                        messagingDestination.setSessionSelector(md.sessionSelector());
166                        initSecurizer(messagingDestination, md.securizer(), configProvider);
167                        messagingDestination.initServices(servicesConfig);
168                        messagingDestination.setName(md.name());
169                        messagingDestination.setTextMessages(md.textMessages());
170                        messagingDestination.setAcknowledgeMode(md.acknowledgeMode());
171                        messagingDestination.setConnectionFactory(md.connectionFactory());
172                        messagingDestination.setTransactedSessions(md.transactedSessions());
173                        messagingDestination.setJndiName(md.topicJndiName());
174                        messagingDestination.initServices(servicesConfig);
175                }
176                else if (field.isAnnotationPresent(ActiveMQTopicDestination.class)) {
177                        ActiveMQTopicDestination md = field.getAnnotation(ActiveMQTopicDestination.class);
178                        AbstractActiveMQTopicDestination messagingDestination = new AbstractActiveMQTopicDestination();
179                        messagingDestination.setId(field.getName());
180                        messagingDestination.setNoLocal(md.noLocal());
181                        messagingDestination.setSessionSelector(md.sessionSelector());
182                        initSecurizer(messagingDestination, md.securizer(), configProvider);
183                        messagingDestination.initServices(servicesConfig);
184                        messagingDestination.setName(md.name());
185                        messagingDestination.setTextMessages(md.textMessages());
186                        messagingDestination.setAcknowledgeMode(md.acknowledgeMode());
187                        messagingDestination.setConnectionFactory(md.connectionFactory());
188                        messagingDestination.setTransactedSessions(md.transactedSessions());
189                        messagingDestination.setJndiName(md.topicJndiName());
190                        messagingDestination.setBrokerUrl(md.brokerUrl());
191                        messagingDestination.setCreateBroker(md.createBroker());
192                        messagingDestination.setDurable(md.durable());
193                        messagingDestination.setWaitForStart(md.waitForStart());
194                        messagingDestination.setFileStoreRoot(md.fileStoreRoot());
195                        messagingDestination.initServices(servicesConfig);
196                }
197        }
198    }
199    
200    private static void initSecurizer(AbstractMessagingDestination messagingDestination, Class<? extends GravityDestinationSecurizer> securizerClass, ConfigProvider configProvider) {
201                if (securizerClass != GravityDestinationSecurizer.class) {
202                        if (configProvider != null)
203                                messagingDestination.setSecurizer(configProvider.findInstance(securizerClass));
204                        if (messagingDestination.getSecurizer() == null)
205                                messagingDestination.setSecurizerClassName(securizerClass.getName());
206                }
207    }
208}