/*
* Copyright 2011 FatWire Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fatwire.gst.foundation.controller.support;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fatwire.gst.foundation.controller.AppContext;
import com.fatwire.gst.foundation.controller.action.support.DefaultWebAppContext;
/**
* ServletContextListener that loads and configures the AppContext for this
* application.
*
* It is NOT configured to auto-load into the servlet context by way of the
* {@code @WebListener} annotation. Overriding of this class is allowed
* but not advised as it is now deprecated.
*
* GSF's own Groovy Loader / support has been deprecated, must not be used anymore.
*
* @author Dolf Dijkstra
* @author Tony Field
*
* @deprecated Replaced by the brand new ServletContextLoader (which requires Servlet 3.0 and differs in usage)
*
*/
public class WebAppContextLoader implements ServletContextListener {
public static final String CONTEXTS = "gsf-contexts";
protected static final Logger LOG = LoggerFactory.getLogger("tools.gsf.legacy.controller.support.WebAppContextLoader");
private static final Class<?>[] ARGS = new Class[] { ServletContext.class, AppContext.class };
public void contextInitialized(final ServletContextEvent sce) {
final ServletContext context = sce.getServletContext();
if (context.getMajorVersion() == 3 && context.getMinorVersion() < 0) {
throw new IllegalStateException("Servlet Container is configured for version less than 3.0. This ServletContextListener does not support 2.x and earlier as the load order of Listeners is not guaranteed.");
}
configureWebAppContext(context);
}
private AppContext configureWebAppContext(final ServletContext context) {
AppContext parent;
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
// start with init param
parent = configureFromInitParam(context, cl);
if (parent != null) {
LOG.info("AppContext initialized from init param. Class: {}", parent.getClass().getName());
}
// try locator
if (parent == null) {
parent = configureFromServiceLocator(context, cl);
if (parent != null) {
LOG.info("AppContext initialized from service locator. Class: {}", parent.getClass().getName());
}
}
// use default
if (parent == null) {
parent = new DefaultWebAppContext(context);
parent.init();
LOG.info("AppContext initialized using default class: {}", parent.getClass().getName());
}
// register it
context.setAttribute(WebAppContext.WEB_CONTEXT_NAME, parent);
return parent;
}
private static final String PREFIX = "META-INF/";
private AppContext configureFromServiceLocator(ServletContext context, ClassLoader cl) {
String fullName = PREFIX + CONTEXTS;
List<String> init = new LinkedList<>();
Enumeration<URL> configs;
try {
configs = cl.getResources(fullName);
} catch (IOException e) {
LOG.warn("Exception when loading the service descriptor for the AppContext from the classpath.", e);
return null;
}
if (configs.hasMoreElements()) {
URL u = configs.nextElement();
try (InputStream in = u.openStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8"))) {
String s;
while ((s = r.readLine()) != null) {
if (StringUtils.isNotBlank(s) && !StringUtils.startsWith(s, "#")) {
init.add(s);
}
}
} catch (IOException e) {
throw new RuntimeException("Error reading configuration file", e);
}
return this.createFromString(context, cl, init.toArray(new String[init.size()]));
}
return null;
}
/**
* Creates a AppContext based on the init parameter {@link #CONTEXTS}.
*
* @param context servlet context
* @param cl class loader
* @return the AppContext as configured from the web app init parameter.
*/
protected AppContext configureFromInitParam(final ServletContext context, final ClassLoader cl) {
final String init = context.getInitParameter(CONTEXTS);
AppContext parent = null;
if (init != null) {
parent = createFromString(context, cl, init.split(","));
}
return parent;
}
private AppContext createFromString(final ServletContext context, final ClassLoader cl, final String[] c) {
AppContext parent = null;
if (c != null) {
for (int i = c.length - 1; i >= 0; i--) {
try {
final AppContext n = createAppContext(cl, c[i], context, parent);
if (n != null) {
parent = n;
}
} catch (IllegalArgumentException
| InstantiationException
| IllegalAccessException
| InvocationTargetException
| SecurityException
| NoSuchMethodException
| ClassNotFoundException e) {
LOG.warn("Couldn't create the app context", e);
}
}
}
return parent;
}
/**
* @param cl the classloader to load the class.
* @param c the class name, class needs to implement a constructor with a
* <tt>context</tt> and a <tt>parent</tt> {@link #ARGS}.
* @param context the web context
* @param parent parent AppContext, null is none
* @return the created AppContext
* @throws ClassNotFoundException class not found
* @throws SecurityException security exception
* @throws NoSuchMethodException no method exists
* @throws InstantiationException could not instantiate
* @throws IllegalAccessException no access to perform this operation
* @throws InvocationTargetException exception from invocation
*/
AppContext createAppContext(final ClassLoader cl, final String c, final ServletContext context,
final AppContext parent) throws ClassNotFoundException, SecurityException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
@SuppressWarnings("unchecked")
final Class<AppContext> cls = (Class<AppContext>) cl.loadClass(c);
final Constructor<AppContext> ctr = cls.getConstructor(ARGS);
AppContext n;
n = ctr.newInstance(context, parent);
n.init();
return n;
}
public void contextDestroyed(final ServletContextEvent sce) {
sce.getServletContext().removeAttribute(WebAppContext.WEB_CONTEXT_NAME);
LOG.info("AppContext un-registered from servlet context.");
}
}