View Javadoc
1   /*
2    * Copyright 2016 Function1. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package tools.gsf.config;
17  
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  
21  import javax.servlet.ServletContext;
22  import javax.servlet.ServletContextEvent;
23  import javax.servlet.ServletContextListener;
24  import javax.servlet.annotation.WebListener;
25  import java.lang.reflect.Constructor;
26  import java.lang.reflect.InvocationTargetException;
27  import java.util.Set;
28  
29  import static tools.gsf.config.ReflectionUtils.readConfigurationResource;
30  
31  /**
32   * ServletContextListener that loads and configures the FactoryProducer for this
33   * application.
34   * <p>
35   * It is configured to auto-load into the servlet context by way of the
36   * {@code @WebListener} annotation.  Overriding of this class is allowed, and overridden
37   * classes that are annotated with {@code @WebListener} will take precedence over this one.
38   * <p>
39   * By default, this class looks for the FactoryProducer class in a servletContext init
40   * parameter called {@link #GSF_FACTORY_PRODUCER}. The class should have a public zero-arg
41   * constructor.
42   * <p>
43   * If no servlet context init parameter is found, this loader will search the classPath for
44   * a resource called {@link #CONFIG_FILE}. This file can contain blank lines and comments
45   * (denoted by #) but must contain only one active line - a class name of a class implementing
46   * the FactoryProducer interface. If more than one class name is found, an exception
47   * will be thrown.
48   * <p>
49   * If neither of the above are found, {@link DefaultFactoryProducer} will be instantiated
50   * as the factory producer.
51   * <p>
52   * When the servlet context is initialized, the factory producer that is created is registered
53   * the servlet context using the parameter {@link #GSF_FACTORY_PRODUCER}, and removed when the
54   * servlet context is destroyed.
55   *
56   * @author Tony Field
57   * @since 2016-08-05
58   */
59  @WebListener
60  public class ServletContextLoader implements ServletContextListener {
61  
62      private static final Logger LOG = LoggerFactory.getLogger(ServletContextLoader.class);
63  
64      /**
65       * Name of the servlet context init parameter containing the factory producer to be booted
66       */
67      public static final String GSF_FACTORY_PRODUCER = "gsf-factory-producer";
68  
69      /**
70       * Name of config file where the factory producer class is configured.
71       */
72      public static final String CONFIG_FILE = "META-INF/gsf-factory-producer";
73  
74      @Override
75      public void contextInitialized(ServletContextEvent servletContextEvent) {
76          final ServletContext context = servletContextEvent.getServletContext();
77          if (context.getMajorVersion() == 3 && context.getMinorVersion() < 0) {
78              throw new IllegalStateException("Servlet Container is configured for version less than 3.0. " +
79                      "This ServletContextListener does not support 2.x and earlier as the load order of " +
80                      "Listeners is not guaranteed.");
81          }
82  
83          FactoryProducer factoryProducer = null;
84  
85          ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
86  
87          if (factoryProducer == null) {
88              factoryProducer = configureFromInitParam(context, classLoader);
89              if (factoryProducer != null) {
90                  LOG.info("FactoryProducer configured from init param: " + factoryProducer.getClass().getName());
91              }
92          }
93  
94          if (factoryProducer == null) {
95              factoryProducer = configureFromServiceLocator(classLoader);
96              if (factoryProducer != null) {
97                  LOG.info("FactoryProducer configured from service locator: " + factoryProducer.getClass().getName());
98              }
99          }
100 
101         if (factoryProducer == null) {
102             factoryProducer = new DefaultFactoryProducer();
103             LOG.info("FactoryProducer defaulting to: " + factoryProducer.getClass().getName());
104         }
105 
106         context.setAttribute(GSF_FACTORY_PRODUCER, factoryProducer);
107 
108     }
109 
110     @Override
111     public void contextDestroyed(ServletContextEvent servletContextEvent) {
112         servletContextEvent.getServletContext().removeAttribute(GSF_FACTORY_PRODUCER);
113         LOG.info("FactoryProducer un-registered from servlet context.");
114     }
115 
116     private FactoryProducer configureFromInitParam(ServletContext servletContext, ClassLoader classLoader) {
117         String factoryProducerClassName = servletContext.getInitParameter(GSF_FACTORY_PRODUCER);
118         return instantiateFactoryProducer(factoryProducerClassName, classLoader);
119     }
120 
121     private FactoryProducer configureFromServiceLocator(ClassLoader classLoader) {
122         Set<String> classes = readConfigurationResource(classLoader, CONFIG_FILE);
123         switch (classes.size()) {
124             case 0: {
125                 // not found
126                 return null;
127             }
128             case 1: {
129                 // found it!
130                 String fpClass = classes.iterator().next();
131                 return instantiateFactoryProducer(fpClass, classLoader);
132             }
133             default: {
134                 // too many
135                 throw new IllegalStateException("Too many lines in the " + CONFIG_FILE +
136                         " configuration file. Only one FactoryProducer class name is allowed.");
137             }
138         }
139     }
140 
141     private static FactoryProducer instantiateFactoryProducer(String className, ClassLoader classLoader) {
142         if (className == null) {
143             return null;
144         }
145         try {
146             @SuppressWarnings("unchecked")
147             Class<FactoryProducer> cls = (Class<FactoryProducer>) classLoader.loadClass(className);
148             Constructor<FactoryProducer> constructor = cls.getConstructor();
149             return constructor.newInstance();
150         } catch (ClassNotFoundException e) {
151             throw new IllegalArgumentException("Could not find FactoryProducer class: " + className, e);
152         } catch (NoSuchMethodException e) {
153             throw new IllegalStateException("Could not locate zero-arg constructor for FactoryProducer in class: " + className, e);
154         } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
155             throw new IllegalStateException("Could not invoke constructor for FactoryProducer class: " + className, e);
156         }
157     }
158 }