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.config;
022
023import java.io.InputStream;
024import java.lang.annotation.Annotation;
025import java.lang.reflect.Method;
026import java.math.BigDecimal;
027import java.math.BigInteger;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.concurrent.ConcurrentHashMap;
036
037import javax.servlet.ServletContext;
038import javax.servlet.ServletContextEvent;
039import javax.servlet.ServletContextListener;
040import javax.servlet.http.HttpSession;
041import javax.servlet.http.HttpSessionEvent;
042import javax.servlet.http.HttpSessionListener;
043import javax.servlet.ServletException;
044
045import org.granite.config.flex.Channel;
046import org.granite.config.flex.Destination;
047import org.granite.config.flex.EndPoint;
048import org.granite.config.flex.Factory;
049import org.granite.config.flex.Service;
050import org.granite.config.flex.ServicesConfig;
051import org.granite.config.flex.ServletServicesConfig;
052import org.granite.config.servlet3.ServerFilter;
053import org.granite.jmx.GraniteMBeanInitializer;
054import org.granite.logging.Logger;
055import org.granite.messaging.amf.io.util.externalizer.BigDecimalExternalizer;
056import org.granite.messaging.amf.io.util.externalizer.BigIntegerExternalizer;
057import org.granite.messaging.amf.io.util.externalizer.LongExternalizer;
058import org.granite.messaging.amf.process.AMF3MessageInterceptor;
059import org.granite.messaging.service.ExceptionConverter;
060import org.granite.messaging.service.ServiceFactory;
061import org.granite.messaging.service.SimpleServiceFactory;
062import org.granite.messaging.service.security.RemotingDestinationSecurizer;
063import org.granite.messaging.service.security.SecurityService;
064import org.granite.messaging.service.tide.TideComponentAnnotatedWithMatcher;
065import org.granite.messaging.service.tide.TideComponentInstanceOfMatcher;
066import org.granite.messaging.service.tide.TideComponentNameMatcher;
067import org.granite.messaging.service.tide.TideComponentTypeMatcher;
068import org.granite.util.TypeUtil;
069import org.granite.util.ServletParams;
070import org.granite.util.XMap;
071
072
073/**
074 * @author William DRAI
075 */
076public class GraniteConfigListener implements ServletContextListener, HttpSessionListener {
077
078    private static final String GRANITE_CONFIG_SHUTDOWN_KEY = GraniteConfig.class.getName() + "_SHUTDOWN";
079    public static final String GRANITE_CONFIG_ATTRIBUTE = "org.granite.config.serverFilter";
080    public static final String GRANITE_CONFIG_PROVIDER_ATTRIBUTE = "org.granite.config.configProvider";
081    public static final String GRANITE_MBEANS_ATTRIBUTE = "registerGraniteMBeans";
082    
083    public static final String GRANITE_SESSION_TRACKING = "org.granite.config.sessionTracking";
084    public static final String GRANITE_SESSION_MAP = "org.granite.config.sessionMap";
085
086    private static final Logger log = Logger.getLogger(GraniteConfigListener.class);
087
088    public void contextInitialized(ServletContextEvent sce) {
089        try {
090            ServletContext context = sce.getServletContext();
091
092            log.info("Initializing GraniteDS...");
093            
094            Class<?> serverFilterClass = (Class<?>)context.getAttribute(GRANITE_CONFIG_ATTRIBUTE);
095            if (serverFilterClass != null)
096                context.setAttribute(ServletGraniteConfig.GRANITE_CONFIG_DEFAULT_KEY, "org/granite/config/servlet3/granite-config-servlet3.xml");
097            
098            GraniteConfig gConfig = ServletGraniteConfig.loadConfig(context);
099            ServletServicesConfig.loadConfig(context);
100            
101            if (serverFilterClass != null)
102                configureServices(context, serverFilterClass);
103            
104            if ("true".equals(sce.getServletContext().getInitParameter(GRANITE_SESSION_TRACKING))) {
105                    Map<String, HttpSession> sessionMap = new ConcurrentHashMap<String, HttpSession>(200);
106                    sce.getServletContext().setAttribute(GRANITE_SESSION_MAP, sessionMap);
107            }
108            
109            if (gConfig.isRegisterMBeans()) {
110                GraniteMBeanInitializer.registerMBeans(context, 
111                                ServletGraniteConfig.getServletConfig(context), 
112                                ServletServicesConfig.getServletConfig(context));
113            }
114
115            log.info("GraniteDS initialized");
116        }
117        catch (Exception e) {
118            throw new RuntimeException("Could not initialize Granite environment", e);
119        }
120    }
121
122    public void contextDestroyed(ServletContextEvent sce) {
123        ServletContext context = sce.getServletContext();
124
125        log.info("Stopping GraniteDS...");
126
127        @SuppressWarnings("unchecked")
128        List<ShutdownListener> listeners = (List<ShutdownListener>)sce.getServletContext().getAttribute(GRANITE_CONFIG_SHUTDOWN_KEY);
129        if (listeners != null) {
130            try {
131                for (ShutdownListener listener : listeners)
132                    listener.stop();
133            }
134            catch (Exception e) {
135                throw new RuntimeException("Could not destroy Granite environment", e);
136            }
137        }
138
139        if (ServletParams.get(context, GRANITE_MBEANS_ATTRIBUTE, Boolean.TYPE, false))
140                GraniteMBeanInitializer.unregisterMBeans(context);
141
142        log.info("GraniteDS stopped");
143    }
144
145    public static synchronized void registerShutdownListener(ServletContext context, ShutdownListener listener) {
146        @SuppressWarnings("unchecked")
147        List<ShutdownListener> listeners = (List<ShutdownListener>)context.getAttribute(GRANITE_CONFIG_SHUTDOWN_KEY);
148        if (listeners == null) {
149            listeners = new ArrayList<ShutdownListener>();
150            context.setAttribute(GRANITE_CONFIG_SHUTDOWN_KEY, listeners);
151        }
152        listeners.add(listener);
153    }
154    
155    
156    private void configureServices(ServletContext servletContext, Class<?> serverFilterClass) throws ServletException {
157        GraniteConfig graniteConfig = ServletGraniteConfig.loadConfig(servletContext);
158        ServicesConfig servicesConfig = ServletServicesConfig.loadConfig(servletContext);
159        
160        ServerFilter serverFilter = serverFilterClass.getAnnotation(ServerFilter.class);
161        
162        ConfigProvider configProvider = null;
163        
164        boolean useTide = serverFilter.tide();
165        String type = serverFilter.type();
166        Class<?> factoryClass = null;
167        Set<Class<?>> tideInterfaces = new HashSet<Class<?>>(Arrays.asList(serverFilter.tideInterfaces()));
168        Set<Class<? extends Annotation>> tideAnnotations = new HashSet<Class<? extends Annotation>>(Arrays.asList(serverFilter.tideAnnotations()));
169        
170        if (!serverFilter.configProviderClass().equals(ConfigProvider.class)) {
171                try {
172                        configProvider = TypeUtil.newInstance(serverFilter.configProviderClass(), new Class[] { ServletContext.class }, new Object[] { servletContext });
173                        
174                        servletContext.setAttribute(GRANITE_CONFIG_PROVIDER_ATTRIBUTE, configProvider);
175                        
176                        if (configProvider.useTide() != null)
177                                useTide = configProvider.useTide();
178                        
179                        if (configProvider.getType() != null)
180                                type = configProvider.getType();
181                        
182                        factoryClass = configProvider.getFactoryClass();
183                        
184                        if (configProvider.getTideInterfaces() != null)
185                                tideInterfaces.addAll(Arrays.asList(configProvider.getTideInterfaces()));
186                        
187                        if (configProvider.getTideAnnotations() != null)
188                                tideAnnotations.addAll(Arrays.asList(configProvider.getTideAnnotations()));
189                        }
190                        catch (Exception e) {
191                                log.error(e, "Could not set config provider of type %s", serverFilter.configProviderClass().getName());
192                        }
193        }
194        
195        if (!serverFilter.factoryClass().equals(ServiceFactory.class))
196                factoryClass = serverFilter.factoryClass();
197        
198        if (factoryClass == null) {
199                factoryClass = SimpleServiceFactory.class;
200                useTide = false;
201        }
202        
203        for (Class<?> ti : tideInterfaces) {
204                try {
205                        graniteConfig.getTideComponentMatchers().add(new TideComponentInstanceOfMatcher(ti.getName(), false));
206                        log.debug("Enabled components implementing %s for Tide remoting", ti);
207                }
208                catch (Exception e) {
209                        log.error(e, "Could not add tide-component interface %s", ti);
210                }
211        }
212        for (Class<? extends Annotation> ta : tideAnnotations) {
213                try {
214                        graniteConfig.getTideComponentMatchers().add(new TideComponentAnnotatedWithMatcher(ta.getName(), false));
215                        log.debug("Enabled components annotated with %s for Tide remoting", ta);
216                }
217                catch (Exception e) {
218                        log.error(e, "Could not add tide-component annotation %s", ta);
219                }
220        }
221        for (String tn : serverFilter.tideNames()) {
222                try {
223                        graniteConfig.getTideComponentMatchers().add(new TideComponentNameMatcher(tn, false));
224                        log.debug("Enabled components with name %s for Tide remoting", tn);
225                }
226                catch (Exception e) {
227                        log.error(e, "Could not add tide-component name %s", tn);
228                }
229        }
230        for (String tt : serverFilter.tideTypes()) {
231                try {
232                        graniteConfig.getTideComponentMatchers().add(new TideComponentTypeMatcher(tt, false));
233                        log.debug("Enabled components with type %s for Tide remoting", tt);
234                }
235                catch (Exception e) {
236                        log.error(e, "Could not add tide-component type %s", tt);
237                }
238        }
239        
240        for (Class<? extends ExceptionConverter> ec : serverFilter.exceptionConverters()) {
241                graniteConfig.registerExceptionConverter(ec, true);
242                        log.debug("Registered exception converter %s", ec);
243        }
244        if (configProvider != null) {
245                for (ExceptionConverter ec : configProvider.findInstances(ExceptionConverter.class)) {
246                        graniteConfig.registerExceptionConverter(ec, true);
247                        log.debug("Registered exception converter %s", ec.getClass());
248                }
249        }
250        
251        if (serverFilter.useBigDecimal())
252                graniteConfig.setExternalizersByType(BigDecimal.class.getName(), BigDecimalExternalizer.class.getName());
253        
254        if (serverFilter.useBigInteger())
255                graniteConfig.setExternalizersByType(BigInteger.class.getName(), BigIntegerExternalizer.class.getName());
256        
257        if (serverFilter.useLong())
258                graniteConfig.setExternalizersByType(Long.class.getName(), LongExternalizer.class.getName());
259        
260        if (!serverFilter.securityServiceClass().equals(SecurityService.class)) {
261                try {
262                        graniteConfig.setSecurityService(TypeUtil.newInstance(serverFilter.securityServiceClass(), SecurityService.class));
263                }
264                catch (Exception e) {
265                        throw new ServletException("Could not setup security service", e);
266                }
267        }
268        else if (graniteConfig.getSecurityService() == null && configProvider != null) {
269                        SecurityService securityService = configProvider.findInstance(SecurityService.class);
270                        graniteConfig.setSecurityService(securityService);
271                }
272        if (graniteConfig.getSecurityService() == null) {
273                String securityServiceClassName = null;
274                // Try auto-detect
275                try {
276                        TypeUtil.forName("org.mortbay.jetty.Request");
277                        securityServiceClassName = "org.granite.messaging.service.security.Jetty6SecurityService";
278                }
279                catch (ClassNotFoundException e1) {
280                        try {
281                                TypeUtil.forName("org.eclipse.jetty.server.Request");
282                                securityServiceClassName = "org.granite.messaging.service.security.Jetty7SecurityService";
283                        }
284                        catch (ClassNotFoundException e1b) {
285                                try {
286                                        TypeUtil.forName("weblogic.servlet.security.ServletAuthentication");
287                                        securityServiceClassName = "org.granite.messaging.service.security.WebLogicSecurityService";
288                                }
289                                catch (ClassNotFoundException e2) {
290                                        try {
291                                                TypeUtil.forName("com.sun.appserv.server.LifecycleEvent");
292                                        securityServiceClassName = "org.granite.messaging.service.security.GlassFishV3SecurityService";
293                                        }
294                                        catch (ClassNotFoundException e3) {
295                                                securityServiceClassName = "org.granite.messaging.service.security.Tomcat7SecurityService";
296                                        }
297                                }
298                                try {
299                                        graniteConfig.setSecurityService((SecurityService)TypeUtil.newInstance(securityServiceClassName));
300                        }
301                        catch (Exception e) {
302                                throw new ServletException("Could not setup security service " + securityServiceClassName, e);
303                        }
304                        }
305                }
306        }
307        
308        if (!serverFilter.amf3MessageInterceptor().equals(AMF3MessageInterceptor.class)) {
309                try {
310                        graniteConfig.setAmf3MessageInterceptor(TypeUtil.newInstance(serverFilter.amf3MessageInterceptor(), AMF3MessageInterceptor.class));
311                }
312                catch (Exception e) {
313                        throw new ServletException("Could not setup amf3 message interceptor", e);
314                }
315        }
316        else if (graniteConfig.getAmf3MessageInterceptor() == null && configProvider != null) {
317                AMF3MessageInterceptor amf3MessageInterceptor = configProvider.findInstance(AMF3MessageInterceptor.class);
318                        graniteConfig.setAmf3MessageInterceptor(amf3MessageInterceptor);
319        }
320        
321        Channel channel = servicesConfig.findChannelById("graniteamf");
322        if (channel == null) {
323                channel = new Channel("graniteamf", "mx.messaging.channels.AMFChannel", 
324                        new EndPoint("http://{server.name}:{server.port}/{context.root}/graniteamf/amf", "flex.messaging.endpoints.AMFEndpoint"), 
325                        new XMap());
326                servicesConfig.addChannel(channel);
327        }
328
329        XMap factoryProperties = new XMap();
330        String lookup = null;
331        if (useTide) {
332                lookup = "java:global/{context.root}/{capitalized.component.name}Bean";
333                if (isJBoss6())
334                        lookup = "{capitalized.component.name}Bean/local";
335                if (!("".equals(serverFilter.ejbLookup())))
336                        lookup = serverFilter.ejbLookup();
337        }
338        else {
339                lookup = "java:global/{context.root}/{capitalized.destination.id}Bean";
340                if (isJBoss6())
341                        lookup = "{capitalized.destination.id}Bean/local";
342                if (!("".equals(serverFilter.ejbLookup())))
343                        lookup = serverFilter.ejbLookup();
344        }
345        if (lookup.indexOf("{context.root}") >= 0) {
346                try {
347                        // Call by reflection because of JDK 1.4
348                        Method m = servletContext.getClass().getMethod("getContextPath");
349                        String contextPath = (String)m.invoke(servletContext);
350                        lookup = lookup.replace("{context.root}", contextPath.substring(1));
351                }
352                catch (Exception e) {
353                        log.error(e, "Could not get context path, please define lookup manually in @FlexFilter");
354                }
355        }
356        factoryProperties.put("lookup", lookup);
357
358        if (useTide) {
359                Factory factory = servicesConfig.findFactoryById("tide-" + type + "-factory");
360                if (factory == null) {
361                        factory = new Factory("tide-" + type + "-factory", factoryClass.getName(), factoryProperties);
362                        servicesConfig.addFactory(factory);
363                }
364                
365                Service service = servicesConfig.findServiceById("granite-service");
366                if (service == null) {
367                        service = new Service("granite-service", "flex.messaging.services.RemotingService", 
368                                        "flex.messaging.messages.RemotingMessage", null, null, new HashMap<String, Destination>());
369                        List<String> channelIds = new ArrayList<String>();
370                        channelIds.add("graniteamf");
371                        List<String> tideRoles = serverFilter.tideRoles().length == 0 ? null : Arrays.asList(serverFilter.tideRoles());
372                        Destination destination = new Destination(type, channelIds, new XMap(), tideRoles, null, null);
373                        destination.getProperties().put("factory", "tide-" + type + "-factory");
374                        if (!("".equals(serverFilter.entityManagerFactoryJndiName())))
375                                destination.getProperties().put("entity-manager-factory-jndi-name", serverFilter.entityManagerFactoryJndiName());
376                        else if (!("".equals(serverFilter.entityManagerJndiName())))
377                                destination.getProperties().put("entity-manager-jndi-name", serverFilter.entityManagerJndiName());
378                        if (!("".equals(serverFilter.validatorClassName())))
379                                destination.getProperties().put("validator-class-name", serverFilter.validatorClassName());
380                        service.getDestinations().put(type, destination);
381                        
382                        if (destination.getSecurizer() == null && configProvider != null) {
383                        RemotingDestinationSecurizer securizer = configProvider.findInstance(RemotingDestinationSecurizer.class);
384                        destination.setSecurizer(securizer);
385                        }
386                        
387                        servicesConfig.addService(service);
388                }
389            
390                if (factoryClass.getName().equals("org.granite.tide.ejb.EjbServiceFactory"))
391                        servicesConfig.scan(null);
392                
393                log.info("Registered Tide " + factoryClass + " service factory and " + type + " destination");
394        }
395        else {
396                Factory factory = new Factory(type + "-factory", factoryClass.getName(), factoryProperties);
397                servicesConfig.addFactory(factory);
398                
399                Service service = new Service("granite-service", "flex.messaging.services.RemotingService", 
400                        "flex.messaging.messages.RemotingMessage", null, null, new HashMap<String, Destination>());
401                servicesConfig.addService(service);
402            
403            servicesConfig.scan(null);
404                
405                log.info("Registered " + factoryClass + " service factory");
406        }
407    }
408        
409        private static boolean isJBoss6() {
410                try {
411                        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/org/jboss/version.properties");
412                        if (is != null)
413                                return true;
414                }
415                catch (Throwable t) {
416                }
417                return false;
418        }
419
420        
421        public void sessionCreated(HttpSessionEvent se) {
422                @SuppressWarnings("unchecked")
423                Map<String, HttpSession> sessionMap = (Map<String, HttpSession>)se.getSession().getServletContext().getAttribute(GRANITE_SESSION_MAP);
424                if (sessionMap != null)
425                        sessionMap.put(se.getSession().getId(), se.getSession());
426        }
427
428        public void sessionDestroyed(HttpSessionEvent se) {
429                @SuppressWarnings("unchecked")
430                Map<String, HttpSession> sessionMap = (Map<String, HttpSession>)se.getSession().getServletContext().getAttribute(GRANITE_SESSION_MAP);
431                if (sessionMap != null)
432                        sessionMap.remove(se.getSession().getId());
433        }
434}