package org.esmerilprogramming.overtown.server.handlers;
import com.google.common.base.Predicate;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.RoutingHandler;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.session.InMemorySessionManager;
import io.undertow.server.session.SessionAttachmentHandler;
import io.undertow.server.session.SessionListener;
import io.undertow.server.session.SessionManager;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ServletContainer;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.esmerilprogramming.overtown.annotation.JSONResponse;
import org.esmerilprogramming.overtown.annotation.Page;
import org.esmerilprogramming.overtown.annotation.path.Get;
import org.esmerilprogramming.overtown.annotation.path.Path;
import org.esmerilprogramming.overtown.annotation.path.Post;
import org.esmerilprogramming.overtown.http.*;
import org.esmerilprogramming.overtown.scanner.PackageScanner;
import org.esmerilprogramming.overtown.scanner.ScannerResult;
import org.esmerilprogramming.overtown.scanner.exception.PackageNotFoundException;
import org.esmerilprogramming.overtown.server.Configuration;
import org.esmerilprogramming.overtown.server.ConfigurationHolder;
import org.esmerilprogramming.overtown.server.ResourceHandlerMounter;
import org.esmerilprogramming.overtown.server.exception.ConfigurationException;
import org.esmerilprogramming.overtown.server.exception.NoControllerException;
import org.esmerilprogramming.overtown.server.mounters.SessionListenerMounter;
import org.esmerilprogramming.overtown.server.mounters.SessionListenerMounterImpl;
import org.jboss.logging.Logger;
import org.reflections.ReflectionUtils;
import org.xnio.ByteBufferSlicePool;
import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
public class StartupHandlerImpl implements StartupHandler {
private static final Logger LOGGER = Logger.getLogger(StartupHandlerImpl.class);
@Override
public Undertow prepareBuild(Configuration configuration) throws IOException {
validateConfiguration(configuration);
ConfigurationHolder.getInstance().prepareConfiguration(configuration);
ScannerResult scannerResult = identifyEligibleClasses( configuration );
OvertownSessionManager instance = OvertownSessionManager.getInstance();
InMemorySessionManager sessionManager = instance.getSessionManager();
sessionManager.setDefaultSessionTimeout( configuration.getMaxSessionTime() );
configureSessionManager(sessionManager, scannerResult.getSessionListeners());
return Undertow.builder()
.addHttpListener( configuration.getPort(), configuration.getHost() )
.setHandler( createRootHandler( configuration , scannerResult ) )
.build();
}
public HttpHandler createRootHandler(Configuration configuration , ScannerResult scannerResult) {
PathHandler pathHandler = Handlers.path();
String appContext = "/" + configuration.getAppContext();
pathHandler.addPrefixPath( appContext , createAppHandlers(scannerResult));
if(!scannerResult.getServerEndpoints().isEmpty()){
DeploymentInfo builder = new DeploymentInfo().setClassLoader(this.getClass().getClassLoader()).setContextPath("/");
WebSocketDeploymentInfo wsDeployInfo = new WebSocketDeploymentInfo();
wsDeployInfo.setBuffers(new ByteBufferSlicePool(100, 1000));
for(Class<?> serverEndpoint : scannerResult.getServerEndpoints() ){
wsDeployInfo.addEndpoint( serverEndpoint );
}
builder.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, wsDeployInfo);
builder.setDeploymentName("websocketDeploy.war");
final ServletContainer container = ServletContainer.Factory.newInstance();
DeploymentManager manager = container.addDeployment(builder);
manager.deploy();
try {
OvertownSessionManager sessionManager = OvertownSessionManager.getInstance();
String wsContextPath = "ws";
if( !appContext.endsWith("/") ){
wsContextPath += appContext + "/" + wsContextPath;
}
pathHandler.addPrefixPath( wsContextPath ,
new SessionAttachmentHandler( manager.start() , sessionManager.getSessionManager(),
sessionManager.getSessionConfig()) );
} catch (ServletException e) {
e.printStackTrace();
}
}
String staticContextPath = configuration.getStaticRootPath();
if( !appContext.endsWith("/") ){
staticContextPath = appContext + "/" + staticContextPath;
}
pathHandler.addPrefixPath( staticContextPath , new ResourceHandlerMounter().mount());
return pathHandler;
}
public ScannerResult identifyEligibleClasses(Configuration configuration) {
ScannerResult scan = null;
try {
scan = new PackageScanner().scan( configuration.getPackageToSkan() );
} catch (PackageNotFoundException | IOException e) {
e.printStackTrace();
LOGGER.error(e.getMessage());
}
return scan;
}
protected void configureSessionManager(SessionManager sessionManager , List<Class<?>> annotatedSessionListeners ){
SessionListenerMounter mounter = new SessionListenerMounterImpl();
for (Class<?> annotatedClass : annotatedSessionListeners) {
SessionListener sl = mounter.mount(annotatedClass);
if(sl != null){
sessionManager.registerSessionListener( sl);
}
}
}
protected void validateConfiguration(Configuration configuration) {
String packageToSkan = configuration.getPackageToSkan();
packageToSkan = packageToSkan == null ? "" : packageToSkan.trim();
if( "".equals( packageToSkan ) ){
throw new ConfigurationException("You should specify the package to be scanned. See https://github.com/EsmerilProgramming/overtown for more info");
}
}
public HttpHandler createAppHandlers(ScannerResult scannerResult){
OvertownSessionManager sessionManager = OvertownSessionManager.getInstance();
HttpHandler error500 = mount500( scannerResult.getInternalErrorClass() );
if( !scannerResult.getControllerMappings().isEmpty() ) {
RoutingHandler rh = new CustomRoutingHandler();
rh.setFallbackHandler(mount404(scannerResult.getNotFoundClass()));
rh.setInvalidMethodHandler( mount405( scannerResult.getMethodNotAllowedClass() ) );
ControllerHandlerCreator chc = new ControllerHandlerCreator();
for(ControllerMapping mapping : scannerResult.getControllerMappings() ){
mapping.setInternalErrorHandler(error500);
rh = chc.createHandler(mapping , rh);
}
return new SessionAttachmentHandler( rh , sessionManager.getSessionManager(),
sessionManager.getSessionConfig());
}
throw new NoControllerException("You should specify at least one controller, verify if you informed the right package to be scanned or see https://github.com/EsmerilProgramming/overtown for more info");
}
protected HttpHandler mount404( Class notFoundClass ){
return createErrorController("404" , notFoundClass , DefaultNotFoundPage.class );
}
protected HttpHandler mount405( Class methodNotAllowed ){
return createErrorController("405" , methodNotAllowed , DefaultMethodNotAllowedPage.class );
}
protected HttpHandler mount500( Class methodNotAllowed ){
return createErrorController("500" , methodNotAllowed , DefaultInternalErrorPage.class );
}
protected HttpHandler createErrorController(String error , Class<?> clazz , Class defaultClass){
clazz = clazz == null ? defaultClass : clazz;
ControllerMapping controllerMapping = new ControllerMapping( error , error);
controllerMapping.setControllerClass( clazz );
try {
Method handlerError = clazz.getMethod("handleError", OvertownRequest.class);
PathMapping methodMapping = new PathMapping( error , null , handlerError , getTemplate(handlerError) , handlerError.getAnnotation(JSONResponse.class) != null );
Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
return new MainHttpHandler( controllerMapping , methodMapping , paranamer.lookupParameterNames( handlerError ) );
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
private String getTemplate( Method notFound ){
Set<Annotation> annotations = ReflectionUtils.getAnnotations(notFound, new Predicate<Annotation>() {
public boolean apply(Annotation input) {
Class<? extends Annotation> aClass = input.annotationType();
return Path.class.equals(aClass) && !((Path) input).template().equals(Path.NO_TEMPLATE)
|| Get.class.equals(aClass) && !((Get) input).template().equals(Path.NO_TEMPLATE)
|| Post.class.equals(aClass) && !((Post) input).template().equals(Path.NO_TEMPLATE)
|| Page.class.equals(aClass) && !((Page) input).responseTemplate().equals(Path.NO_TEMPLATE);
}
});
String template = Path.NO_TEMPLATE;
if(annotations.size() > 0) {
Annotation next = annotations.iterator().next();
if (Path.class.equals(next.annotationType())) {
template = ((Path) next).template();
}
if (Get.class.equals(next.annotationType())) {
template = ((Get) next).template();
}
if (Post.class.equals(next.annotationType())) {
template = ((Post) next).template();
}
if (Page.class.equals(next.annotationType())) {
template = ((Page) next).responseTemplate();
}
}
return template;
}
}