/**
* Copyright (C) 2012-2014 the original author or authors.
*
* 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 ninja.servlet;
import java.util.ArrayList;
import java.util.List;
import ninja.Configuration;
import ninja.Context;
import ninja.Ninja;
import ninja.NinjaDefault;
import ninja.Router;
import ninja.application.ApplicationRoutes;
import ninja.lifecycle.LifecycleSupport;
import ninja.logging.LogbackConfigurator;
import ninja.scheduler.SchedulerSupport;
import ninja.utils.NinjaConstant;
import ninja.utils.NinjaPropertiesImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.servlet.ServletModule;
public class NinjaBootstrap {
private static final Logger logger = LoggerFactory.getLogger(NinjaBootstrap.class);
private static final String APPLICATION_GUICE_MODULE_CONVENTION_LOCATION = "conf.Module";
private static final String APPLICATION_GUICE_SERVLET_MODULE_CONVENTION_LOCATION = "conf.ServletModule";
private static final String ROUTES_CONVENTION_LOCATION = "conf.Routes";
private static final String NINJA_CONVENTION_LOCATION = "conf.Ninja";
private final NinjaPropertiesImpl ninjaProperties;
private Injector injector = null;
public NinjaBootstrap(NinjaPropertiesImpl ninjaProperties) {
Preconditions.checkNotNull(ninjaProperties);
this.ninjaProperties = ninjaProperties;
}
public Injector getInjector() {
return injector;
}
public synchronized void boot() {
initLogbackIfLogbackIsOnTheClassPathOtherwiseDoNotInitLogging();
if (injector != null) {
throw new RuntimeException("NinjaBootstap already booted");
}
long startTime = System.currentTimeMillis();
injector = initInjector();
long injectorStartupTime = System.currentTimeMillis() - startTime;
logger.info("Ninja injector started in " + injectorStartupTime + " ms.");
Preconditions.checkNotNull(injector, "Ninja injector cannot be generated. Please check log for further errors.");
Ninja ninja = injector.getInstance(Ninja.class);
ninja.onFrameworkStart();
}
public synchronized void shutdown() {
if (injector != null) {
Ninja ninja = injector.getInstance(Ninja.class);
ninja.onFrameworkShutdown();
injector = null;
ninja = null;
} else {
logger.error("Shutdown of Ninja not clean => injector already null.");
}
}
private Injector initInjector() {
try {
List<Module> modulesToLoad = new ArrayList<>();
// Bind lifecycle support
modulesToLoad.add(LifecycleSupport.getModule());
// Scheduling support
modulesToLoad.add(SchedulerSupport.getModule());
// Get base configuration of Ninja:
modulesToLoad.add(new Configuration(ninjaProperties));
modulesToLoad.add(new AbstractModule() {
@Override
protected void configure() {
bind(Context.class).to(ContextImpl.class);
}
});
// get custom base package for application modules and routes
Optional<String> applicationModulesBasePackage
= Optional.fromNullable(ninjaProperties.get(
NinjaConstant.APPLICATION_MODULES_BASE_PACKAGE));
// Load main application module:
String applicationConfigurationClassName = getClassNameWithOptionalUserDefinedPrefix(
applicationModulesBasePackage,
APPLICATION_GUICE_MODULE_CONVENTION_LOCATION);
if (doesClassExist(applicationConfigurationClassName)) {
Class<?> applicationConfigurationClass = Class
.forName(applicationConfigurationClassName);
AbstractModule applicationConfiguration = (AbstractModule) applicationConfigurationClass
.getConstructor().newInstance();
modulesToLoad.add(applicationConfiguration);
}
// Load servlet module. By convention this is a ServletModule where
// the user can register other servlets and servlet filters
// If the file does not exist we simply load the default servlet
String servletModuleClassName =
getClassNameWithOptionalUserDefinedPrefix(
applicationModulesBasePackage,
APPLICATION_GUICE_SERVLET_MODULE_CONVENTION_LOCATION);
if (doesClassExist(servletModuleClassName)) {
Class<?> servletModuleClass = Class
.forName(servletModuleClassName);
ServletModule servletModule = (ServletModule) servletModuleClass
.getConstructor().newInstance();
modulesToLoad.add(servletModule);
} else {
// The servlet Module does not exist => we load the default one.
ServletModule servletModule = new ServletModule() {
@Override
protected void configureServlets() {
bind(NinjaServletDispatcher.class).asEagerSingleton();
serve("/*").with(NinjaServletDispatcher.class);
}
};
modulesToLoad.add(servletModule);
}
initializeUserSuppliedConfNinjaOrNinjaDefault(
applicationModulesBasePackage,
modulesToLoad);
// And let the injector generate all instances and stuff:
injector = Guice.createInjector(Stage.PRODUCTION, modulesToLoad);
initializeRouterWithRoutesOfUserApplication(applicationModulesBasePackage);
return injector;
} catch (Exception exception) {
logger.error("Fatal error booting Ninja", exception);
}
return null;
}
private boolean doesClassExist(String nameWithPackage) {
boolean exists = false;
try {
Class.forName(nameWithPackage, false, this.getClass()
.getClassLoader());
exists = true;
} catch (ClassNotFoundException e) {
exists = false;
}
return exists;
}
private String getClassNameWithOptionalUserDefinedPrefix(
Optional<String> optionalUserDefinedPrefixForPackage,
String classLocationAsDefinedByNinja) {
if (optionalUserDefinedPrefixForPackage.isPresent()) {
return new StringBuilder(
optionalUserDefinedPrefixForPackage.get())
.append('.')
.append(classLocationAsDefinedByNinja)
.toString();
} else {
return classLocationAsDefinedByNinja;
}
}
private void initLogbackIfLogbackIsOnTheClassPathOtherwiseDoNotInitLogging() {
// init logging at the very very top
try {
Class.forName("ch.qos.logback.classic.joran.JoranConfigurator");
LogbackConfigurator.initConfiguration(ninjaProperties);
logger.info("Successfully configured Logback.");
// It is available
} catch (ClassNotFoundException exception) {
logger.info(
"Logback is not on classpath (you are probably using slf4j-jdk14). I did not configure anything. It's up to you now...", exception);
}
}
private final void initializeUserSuppliedConfNinjaOrNinjaDefault(
Optional<String> applicationModulesBasePacakge,
List<Module> modulesToLoad) throws Exception {
String ninjaClassName =
getClassNameWithOptionalUserDefinedPrefix(
applicationModulesBasePacakge,
NINJA_CONVENTION_LOCATION);
final Class<? extends Ninja> ninjaClass;
if (doesClassExist(ninjaClassName)) {
final Class<?> clazzPotentially = Class.forName(ninjaClassName);
if (Ninja.class.isAssignableFrom(clazzPotentially)) {
ninjaClass = (Class<? extends Ninja>) clazzPotentially;
} else {
final String ERROR_MESSAGE = String.format(
"Found a class %s in your application's conf directory."
+ " This class does not implement Ninja interface %s. "
+ " Please implement the interface or remove the class.",
ninjaClassName,
Ninja.class.getName());
logger.error(ERROR_MESSAGE);
throw new IllegalStateException(ERROR_MESSAGE);
}
} else {
ninjaClass = NinjaDefault.class;
}
modulesToLoad.add(new AbstractModule() {
@Override
protected void configure() {
bind(Ninja.class).to(ninjaClass).in(Singleton.class);
}
});
}
public void initializeRouterWithRoutesOfUserApplication(
Optional<String> applicationModulesBasePackage)
throws Exception {
// Init routes
String routesClassName =
getClassNameWithOptionalUserDefinedPrefix(
applicationModulesBasePackage,
ROUTES_CONVENTION_LOCATION);
if (doesClassExist(routesClassName)) {
Class<?> clazz = Class.forName(routesClassName);
ApplicationRoutes applicationRoutes = (ApplicationRoutes) injector
.getInstance(clazz);
Router router = injector.getInstance(Router.class);
applicationRoutes.init(router);
router.compileRoutes();
}
}
}