/*
* Copyright (C) 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 ro.pippo.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.gzip.GZipRequestResponseFactory;
import ro.pippo.core.route.DefaultRouter;
import ro.pippo.core.route.ResourceRouting;
import ro.pippo.core.route.Route;
import ro.pippo.core.route.RouteContext;
import ro.pippo.core.route.RouteDispatcher;
import ro.pippo.core.route.RouteGroup;
import ro.pippo.core.route.RouteHandler;
import ro.pippo.core.route.RoutePostDispatchListenerList;
import ro.pippo.core.route.RoutePreDispatchListenerList;
import ro.pippo.core.route.RouteTransformer;
import ro.pippo.core.route.Router;
import ro.pippo.core.util.HttpCacheToolkit;
import ro.pippo.core.util.MimeTypes;
import ro.pippo.core.util.ServiceLocator;
import ro.pippo.core.websocket.WebSocketHandler;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for all Pippo applications.
* From here, you can make some configuration changes or enhancements (for example: custom router,
* custom request, custom response, ..) and add routes.
*
* @author Decebal Suiu
*/
public class Application implements ResourceRouting {
private static final Logger log = LoggerFactory.getLogger(Application.class);
private PippoSettings pippoSettings;
private Languages languages;
private Messages messages;
private MimeTypes mimeTypes;
private HttpCacheToolkit httpCacheToolkit;
private TemplateEngine templateEngine;
private ContentTypeEngines engines;
protected Router router;
private ErrorHandler errorHandler;
private RequestResponseFactory requestResponseFactory;
private ServletContext servletContext;
private List<Initializer> initializers;
private String uploadLocation = System.getProperty("java.io.tmpdir");
private long maximumUploadSize = -1L;
private RoutePreDispatchListenerList routePreDispatchListeners;
private RoutePostDispatchListenerList routePostDispatchListeners;
private Map<String, Object> locals;
private RouteHandler notFoundRouteHandler;
private Map<String, WebSocketHandler> webSocketHandlers;
public Application() {
this(new PippoSettings(RuntimeMode.getCurrent()));
}
public Application(PippoSettings settings) {
this.pippoSettings = settings;
this.languages = new Languages(settings);
this.messages = new Messages(languages);
this.mimeTypes = new MimeTypes(settings);
this.httpCacheToolkit = new HttpCacheToolkit(settings);
this.engines = new ContentTypeEngines();
this.initializers = new ArrayList<>();
this.webSocketHandlers = new HashMap<>();
registerContentTypeEngine(TextPlainEngine.class);
}
public final void init() {
// add initializers
initializers.addAll(ServiceLocator.locateAll(Initializer.class));
// call each initializer
for (Initializer initializer : initializers) {
log.debug("Initializing '{}'", initializer.getClass().getName());
try {
initializer.init(this);
} catch (Exception e) {
log.error("Failed to initialize '{}'", initializer.getClass().getName(), e);
}
}
// add transformers
List<RouteTransformer> transformers = ServiceLocator.locateAll(RouteTransformer.class);
for (RouteTransformer transformer : transformers) {
getRouter().addRouteTransformer(transformer);
}
onInit();
// compile routes
getRouter().compileRoutes();
}
public final void destroy() {
onDestroy();
for (Initializer initializer : initializers) {
log.debug("Destroying '{}'", initializer.getClass().getName());
try {
initializer.destroy(this);
} catch (Exception e) {
log.error("Failed to destroy '{}'", initializer.getClass().getName(), e);
}
}
}
protected void onInit() {
}
protected void onDestroy() {
}
/**
* The runtime mode. Must currently be either DEV, TEST, or PROD.
*/
public RuntimeMode getRuntimeMode() {
return pippoSettings.getRuntimeMode();
}
public PippoSettings getPippoSettings() {
return pippoSettings;
}
public String getApplicationName() {
return pippoSettings.getString(PippoConstants.SETTING_APPLICATION_NAME, "");
}
public String getApplicationVersion() {
return pippoSettings.getString(PippoConstants.SETTING_APPLICATION_VERSION, "");
}
public Languages getLanguages() {
return languages;
}
public Messages getMessages() {
return messages;
}
public MimeTypes getMimeTypes() {
return mimeTypes;
}
public HttpCacheToolkit getHttpCacheToolkit() {
return httpCacheToolkit;
}
/**
* Registers a template engine if no other engine has been registered.
*
* @param engineClass
*/
public void registerTemplateEngine(Class<? extends TemplateEngine> engineClass) {
if (templateEngine != null) {
log.debug("Template engine already registered, ignoring '{}'", engineClass.getName());
return;
}
try {
TemplateEngine engine = engineClass.newInstance();
setTemplateEngine(engine);
} catch (Exception e) {
throw new PippoRuntimeException(e, "Failed to instantiate '{}'", engineClass.getName());
}
}
public TemplateEngine getTemplateEngine() {
return templateEngine;
}
public void setTemplateEngine(TemplateEngine templateEngine) {
templateEngine.init(this);
this.templateEngine = templateEngine;
log.debug("Template engine is '{}'", templateEngine.getClass().getName());
}
public ContentTypeEngines getContentTypeEngines() {
return engines;
}
public boolean hasContentTypeEngine(String contentType) {
return engines.hasContentTypeEngine(contentType);
}
public void registerContentTypeEngine(Class<? extends ContentTypeEngine> engineClass) {
ContentTypeEngine engine = engines.registerContentTypeEngine(engineClass);
if (engine != null) {
engine.init(this);
}
}
public ContentTypeEngine getContentTypeEngine(String contentType) {
return engines.getContentTypeEngine(contentType);
}
public Router getRouter() {
if (router == null) {
router = new DefaultRouter();
}
return router;
}
public void setRouter(Router router) {
setRouter(router, true);
}
public void setRouter(Router router, boolean preserveOldTransformers) {
if (preserveOldTransformers && (router != null)) {
// preserve route transformers already registered
List<RouteTransformer> transformers = this.router.getRouteTransformers();
transformers.forEach(router::addRouteTransformer);
}
this.router = router;
}
@Override
public void addRoute(Route route) {
getRouter().addRoute(route);
}
@Override
public void addRouteGroup(RouteGroup routeGroup) {
getRouter().addRouteGroup(routeGroup);
}
public ErrorHandler getErrorHandler() {
if (errorHandler == null) {
errorHandler = new DefaultErrorHandler(this);
}
return errorHandler;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
public final RequestResponseFactory getRequestResponseFactory() {
if (requestResponseFactory == null) {
requestResponseFactory = createRequestResponseFactory();
}
return requestResponseFactory;
}
/**
* Override this method if you want a custom RequestResponseFactory.
*
* @return
*/
protected RequestResponseFactory createRequestResponseFactory() {
// return new RequestResponseFactory(this);
return new GZipRequestResponseFactory(this);
}
/**
* The directory location where files will be stored.
*
* @return
*/
public String getUploadLocation() {
return uploadLocation;
}
public void setUploadLocation(String uploadLocation) {
this.uploadLocation = uploadLocation;
}
/**
* Gets the maximum size allowed for uploaded files.
*
* @return
*/
public long getMaximumUploadSize() {
return maximumUploadSize;
}
public void setMaximumUploadSize(long maximumUploadSize) {
this.maximumUploadSize = maximumUploadSize;
}
public RoutePreDispatchListenerList getRoutePreDispatchListeners() {
if (routePreDispatchListeners == null) {
routePreDispatchListeners = new RoutePreDispatchListenerList();
}
return routePreDispatchListeners;
}
public RoutePostDispatchListenerList getRoutePostDispatchListeners() {
if (routePostDispatchListeners == null) {
routePostDispatchListeners = new RoutePostDispatchListenerList();
}
return routePostDispatchListeners;
}
public Map<String, Object> getLocals() {
if (locals == null) {
locals = new HashMap<>();
}
return locals;
}
/**
* Returns the servlet context for this application.
* The servlet context is available after instantiation, so DON'T use this method in constructor
* because it returns null.
*
* @return The servlet context or null
*/
public ServletContext getServletContext() {
return servletContext;
}
void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* Helper method that calls {@code getRouter().addRouteTransformer(transformer)}.
*
* @param transformer
*/
public void addRouteTransformer(RouteTransformer transformer) {
getRouter().addRouteTransformer(transformer);
}
/**
* Set the {@link RouteHandler} that is called only if no route has been found for a request.
* It's named {@code Catch-All} route handler.
*
* @param routeHandler
*/
public void setNotFoundRouteHandler(RouteHandler routeHandler) {
this.notFoundRouteHandler = routeHandler;
}
/**
* Returns the {@code Catch-All} route handler or null.
*
* @return
*/
public RouteHandler getNotFoundRouteHandler() {
return notFoundRouteHandler;
}
public void addWebSocket(String path, WebSocketHandler webSocketHandler) {
webSocketHandlers.put(path, webSocketHandler);
}
public WebSocketHandler getWebSocketHandler(String path) {
return webSocketHandlers.get(path);
}
/**
* Returns not null only in the context of the web layer (on a HTTP request).
* It cannot be useful in a service (server side business layer).
* For example if want to have access to PippoSettings from a service you must to inject PippoSettings
* in that service and not to use Application.get().getPippoSettings().
*
* @return The application instance or null
*/
public static Application get() {
RouteContext routeContext = RouteDispatcher.getRouteContext();
return (routeContext != null) ? routeContext.getApplication() : null;
}
@Override
public String toString() {
String toString = (getApplicationName() + " " + getApplicationVersion()).trim();
return toString.isEmpty() ? super.toString() : toString;
}
}