/*
* 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.reload.ReloadClassLoader;
import ro.pippo.core.reload.ReloadWatcher;
import ro.pippo.core.route.ResourceRouting;
import ro.pippo.core.route.Route;
import ro.pippo.core.route.RouteGroup;
import ro.pippo.core.util.ServiceLocator;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Decebal Suiu
*/
public class Pippo implements ResourceRouting, ReloadWatcher.Listener {
private static final Logger log = LoggerFactory.getLogger(Pippo.class);
private Application application;
private WebServer server;
private volatile boolean running;
private volatile boolean reloading;
private ReloadWatcher reloadWatcher;
public Pippo() {
addShutdownHook();
}
public Pippo(Application application) {
this.application = application;
log.debug("Application '{}'", application);
addShutdownHook();
}
public Application getApplication() {
if (application == null) {
initReloading();
if (reloading) {
log.info("Reload enabled");
application = createApplication();
} else {
log.debug("Create a default application");
application = new Application();
}
}
return application;
}
public WebServer getServer() {
if (server == null) {
WebServer server = ServiceLocator.locate(WebServer.class);
if (server == null) {
throw new PippoRuntimeException("Cannot find a WebServer");
}
setServer(server);
}
return server;
}
/**
* Entry point for a custom WebServer.
* The idea is to create a custom WebServer if you want to override some aspects (method) of that server or
* if you want free access to the servlet container (Jetty, Tomcat, ...).
*
* <p>
* Show below the code for a <@code>JettyServer</@code> with persistent sessions.
* </p>
*
* <pre>
* <@code>
* public class MyJettyServer extends JettyServer {
*
* @Override
* protected ServletContextHandler createPippoHandler() {
* ServletContextHandler handler = super.createPippoHandler();
*
* // set session manager with persistence
* HashSessionManager sessionManager = new HashSessionManager();
* try {
* sessionManager.setStoreDirectory(new File("sessions-storage"));
* } catch (IOException e) {
* throw new PippoRuntimeException(e);
* }
* sessionManager.setLazyLoad(true); // other possible option
* handler.setSessionHandler(new SessionHandler(sessionManager));
*
* return handler;
* }
*
* }
*
* public class Main {
*
* public static void main(String[] args) {
* new Pippo(new MyApplication()).setServer(new MyJettyServer()).start();
* }
*
* }
* </@code>
* </pre>
*
* @param server
* @return
*/
public Pippo setServer(WebServer server) {
this.server = server;
this.server.init(getApplication());
return this;
}
/**
* Start the web server on this port.
*
* @param port
*/
public void start(int port) {
getServer().setPort(port);
start();
}
public void start() {
if (running) {
log.warn("Server is already started");
return;
}
WebServer server = getServer();
log.debug("Start server '{}'", server);
server.start();
running = true;
if (reloading) {
startReloadWatcher();
}
}
public void stop() {
if (!running) {
log.warn("Server is not started");
return;
}
if (reloading) {
stopReloadWatcher();
}
WebServer server = getServer();
log.debug("Stop server '{}'", server);
server.stop();
running = false;
}
@Override
public void addRoute(Route route) {
getApplication().addRoute(route);
}
@Override
public void addRouteGroup(RouteGroup routeGroup) {
getApplication().addRouteGroup(routeGroup);
}
public Pippo setFilterPath(String filterPath) {
getServer().setPippoFilterPath(filterPath);
return this;
}
/**
* Create a pippo instance, add a route on "/" that responds with a message.
*
* @param text
* @return
*/
public static Pippo send(final String text) {
Pippo pippo = new Pippo();
pippo.GET("/", routeContext -> routeContext.send(text));
pippo.start();
return pippo;
}
protected void startReloadWatcher() {
if (reloadWatcher == null) {
reloadWatcher = createReloadWatcher();
}
reloadWatcher.start();
}
protected void stopReloadWatcher() {
if (reloadWatcher != null) {
reloadWatcher.stop();
reloadWatcher = null;
}
}
protected ReloadWatcher createReloadWatcher() {
return new ReloadWatcher.Builder()
.addDirectory(System.getProperty(PippoConstants.SYSTEM_PROPERTY_RELOAD_TARGET_CLASSES, "target/classes"))
.build(this);
}
@Override
public void onEvent(ReloadWatcher.Event event, Path dir, Path path) {
stop();
// TODO: very important (I cannot delete this block)
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
// ignore
}
application = createApplication();
getServer().getPippoFilter().setApplication(application);
start();
}
private void addShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(Pippo.this::stop));
}
private Application createApplication() {
String targetClasses = System.getProperty(PippoConstants.SYSTEM_PROPERTY_RELOAD_TARGET_CLASSES, "target/classes");
log.debug("Target classes is '{}'", targetClasses);
String rootPackageName = System.getProperty(PippoConstants.SYSTEM_PROPERTY_RELOAD_ROOT_PACKAGE_NAME, "");
log.debug("Root package name is '{}'", rootPackageName);
ClassLoader classLoader = new ReloadClassLoader(Pippo.class.getClassLoader(), rootPackageName) {
@Override
protected InputStream getInputStream(String path) {
Path resolvedPath = Paths.get(targetClasses).resolve(path);
if (Files.notExists(resolvedPath)) {
return null;
}
try {
return Files.newInputStream(resolvedPath);
} catch (IOException e) {
throw new PippoRuntimeException(e);
}
}
};
Application application = ServiceLocator.locate(Application.class, classLoader);
if (application != null) {
return application;
}
String applicationClassName = System.getProperty(PippoConstants.SYSTEM_PROPERTY_APPLICATION_CLASS_NAME);
if (applicationClassName == null) {
throw new PippoRuntimeException("Cannot find the application class name");
}
log.debug("Application class name is '{}'", applicationClassName);
try {
Class<?> applicationClass = classLoader.loadClass(applicationClassName);
application = (Application) applicationClass.newInstance();
} catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return application;
}
private void initReloading() {
String reloadEnabled = System.getProperty(PippoConstants.SYSTEM_PROPERTY_RELOAD_ENABLED);
if (reloadEnabled != null) {
reloading = Boolean.valueOf(reloadEnabled);
} else {
reloading = RuntimeMode.DEV == RuntimeMode.getCurrent();
}
}
}