Files
Amer Agovic 5f36b3d3e2 Add WebSocket support with Jakarta WebSocket integration
- Implemented WebSocketSession abstract class with callback-based API
- Added ServletWebSocketSession with full Jakarta WebSocket bridging
- Created @WebSocket annotation for declarative endpoint marking
- Updated JettyApp to initialize Jakarta WebSocket container
- Split Request/Response into abstract base and servlet implementations
- Moved JettyApp to jabba.servlet package
- Moved annotations to jabba.decor package
- Added comprehensive WebSocket test suite (5 tests, all passing)
- Updated README.md with WebSocket documentation and examples
- All 31 tests passing (async, sync, security, websocket, database)
- Fixed spelling errors in README.md
2026-01-07 08:57:12 -06:00

142 lines
3.9 KiB
Groovy

/** we define some extra tasks to be imported into main build or shared. */
// load .env settings - mostly secrets
task dotenv{
def ef=file('.env');
if(!ef.exists()) ef=file('../.env');
ef.readLines().each() {
if(it.isEmpty() || it.startsWith("#")) return true;
def (key,value)=it.tokenize('=')
project.ext.set(key,value)
}
}
task packageJavadoc(type: Jar, dependsOn: 'javadoc') {
from javadoc
archiveClassifier = 'javadoc'
}
task packageSources(type: Jar, dependsOn: 'classes') {
from sourceSets.main.allSource
archiveClassifier = 'sources'
}
task fat_jar(type: Jar) {
archiveBaseName = 'fat-'+project.name
archiveVersion = project.version
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
/*
manifest {
attributes "Main-Class": mainClassName
attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
}
*/
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}{
exclude "META-INF/NOTICE.txt"
exclude "META-INF/LICENSE"
}
with jar
}
/**
We define a singleton pattern using background thread to launch a blocking process.
Later we make sure to terminate previous running driver threads before starting new.
Also we split start, stop because we might not run in continouse mode.
*/
class Server implements Runnable{
static Server singleton=null;
public static Server main(){
if(singleton==null) singleton=new Server();
return singleton;
}
//org.slf4j.Logger log=null;
Thread driver=null;
Runnable task=null;
protected Server(){}
public void info(String msg){
//if(log!=null) log.info(msg); else println(msg);
println(msg);
}
//public Server setLogger(org.slf4j.Logger l){
// log=l;
// return this;
//}
public Server useDriver(boolean f){
info("using driver:"+f);
if(f){
driver=new Thread(this);
driver.setName("server.driver");
}else{
driver=null;
}
return this;
}
public void run(){
info("running task");
try{
task.run();
}catch(java.lang.Exception ex){
info("running task:interrupted");
}
}
public Server start(Runnable c){
info("starting server");
this.task=c;
if(driver!=null){
driver.start();
}else{
this.run();
}
return this;
}
public Server stop(){
info("stopping server");
if(driver!=null){
driver.interrupt();
try{
driver.join(5000); // Wait up to 5 seconds for graceful shutdown
}catch(InterruptedException e){
info("interrupted while waiting for driver to stop");
}
}
// Clean up stale threads using proper interruption
for(Thread th:Thread.getAllStackTraces().keySet()){
if(th.getName().equalsIgnoreCase("executor")){
info("cleaning up stale driver:"+th.toString())
th.interrupt();
try{
th.join(2000); // Wait up to 2 seconds
}catch(InterruptedException e){
// Ignore
}
}
if(th.getName().equalsIgnoreCase("server.driver")){
info("cleaning up stale driver:"+th.toString())
th.interrupt();
try{
th.join(2000); // Wait up to 2 seconds
}catch(InterruptedException e){
// Ignore
}
}
}
return this;
}
}
task runServer{
inputs.files 'src'
doFirst {
boolean threaded=project.gradle.startParameter.continuous;
Server.main().stop().useDriver(threaded);//.setLogger(logger);
}
doLast {
Server.main().start({
println(application.mainClass.get())
project.javaexec {
classpath = sourceSets.main.runtimeClasspath
main = application.mainClass.get()
}
});
}
}