Table of contents
jl
This is an experimental web framework by me, the documentation for my own use.
Hello World
create a bundle with the following Activator for hello world.
public class Activator extends LightningActivator {
// optional constructor: if absent, last part of package name is used
public Activator() {
// all routes for this bundle are accessible under /hello
super("hello");
}
@Override
protected void init(BundleContext context) throws Exception {
addPageService(builder -> {
// return a simple String result on requests for /hello
builder.newIdentityRouteGet("", () -> new PageController() {
@Override
public void invoke(ResponseBuilder response) throws Exception {
response.setRenderer(new StringRenderer("hello world! " + new Date()));
}
});
// allow access (true) regardless of context (e.g. ip, sessionId) on all routes
builder.setSecurity(new PageSecurity((route, ctx) -> true));
});
}
}
mapping HTTP requests to controllers
routes
mapping a HTTP method to a relative path under bundle root
builder.newRoute(routeId).map(path, RouteType.GET, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.POST, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.HEAD, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.PUT, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.DELETE, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.TRACE, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.OPTIONS, () -> new PageController() { ... });
builder.newRoute(routeId).map(path, RouteType.CONNECT, () -> new PageController() { ... });
mapping with wildcard in path (run PageController for all pages starting with [bundle-name]/foo/)
builder.newRoute(routeId).mapGet("foo/*", () -> new PageController() {
shorthand: map GET:/[bundle-name]/[path] to controller
builder.newIdentityRouteGet(path, () -> new PageController() { ... }
shorthand: map POST:/[bundle-name] to controller
builder.newIdentityRoutePost(path, () -> new PageController() { ... }
shorthand: map both GET and POST requests to the same controller
builder.newIdentityRouteGetAndPost(path, () -> new PageController() { ... }
associated controller
public class StringRendererController implements PageController {
@Override
public void invoke(ResponseBuilder builder) {
builder.setRenderer(new StringRenderer("Hello world!<br/>" + new Date()));
}
}
dependency injection
public class BeanController implements PageController {
@Bean
private HttpServletRequest request;
@Bean
private HttpServletResponse response;
@Bean
private HttpSession session;
@Bean
private RequestContext context;
@Bean
private Parameters parameters;
@Bean
private BeanInjector injector;
@Bean
private ServiceCollection<MyServiceType> osgiService;
@Override
public void invoke(ResponseBuilder rb) {
....
annotate a java bean with @Singleton in order to inject it into a PageController
@Singleton
public class MyBean {
...
basic renderers
render a simple String in the response
new StringRenderer("...")
render a (freemarker) template as response
new TemplateRenderer(template)
create file download response (renderer)
new DownloadRenderer(new File("/path/to/file.txt"))
new DownloadRenderer(new FileDownload("download-name.txt", new File("/path/to/file.txt")))
new InputStreamRenderer(new FileInputStream("/path/to/file.txt"))
redirects
performing an internal redirect from PageController
public void invoke(ResponseBuilder builder) {
throw new InternalRedirectException(new UrlMapping("internal", "/mvc/prefix/example"));
}
performing an external redirect from PageController
public void invoke(ResponseBuilder builder) {
throw new ExternalRedirectException(new UrlMapping("github", "http://www.github.com"));
}
template engine (freemarker)
template hello world
public class Activator extends LightningActivator {
public Activator() {
super("hello");
}
@Override
protected void init(BundleContext context) throws Exception {
addTemplateSource();
addPageService(builder -> {
builder.newIdentityRouteGet("template", () -> new AbstractTemplateController() {
@Override
public void invoke() throws Exception {
model.put("key", "value");
// template will be loaded from this bundles classpath /hello/template.ftlh
setTemplate("/hello/template.ftlh");
}
});
builder.setSecurity(new PageSecurity((route, ctx) -> true));
});
}
}
making (freemarker) templates available in the activator
addTemplateSource();
exposing a global variable to all templates
addTemplateSource(new TemplateGlobal() {
@Override
public String getName() {
return "key";
}
@Override
public Object getObject() {
return "global";
}
});
referencing the global in the (freemarker) template
${globals.get("date")?time}
referencing a HTTP parameter in the template
${parameter.get('key')}
${parameter.get('key', 'default')}
does a parameter have a specific value?
${parameter.is('key', 'value') ? then ('a', 'b')}
getting the remote url of the host
${lightning.getHost()}
getting the path of current page
${lightning.getCurrentPath()}
getting the remote url of a route
${lightning.getRemotePath('service', 'id')}
note: relevant classes can be examined in FreemarkerTemplateService
websockets
registering a websocket in the activator
addServiceWithCleanup(WebSocketService.class, new HelloSocketService());
Service implementation
import java.util.function.Supplier;
import com.eriklievaart.jl.core.api.websocket.WebSocketService;
public class HelloSocketService implements WebSocketService {
@Override
public String getPath() {
return "/web/socket";
}
@Override
public Supplier<?> webSocketSupplier() {
return () -> new HelloSocket();
}
}
websocket implementation
import java.io.IOException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
public class HelloSocket implements WebSocketListener {
private Session session;
@Override
public void onWebSocketClose(int status, String reason) {
System.out.println("Session " + session.hashCode() + " has ended");
}
@Override
public void onWebSocketConnect(Session s) {
this.session = s;
System.out.println(session.hashCode() + " has opened a connection");
try {
session.getRemote().sendString("Connection Established");
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public void onWebSocketError(Throwable t) {
t.printStackTrace();
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
}
@Override
public void onWebSocketText(String message) {
System.out.println("Message from " + session.hashCode() + ": " + message);
try {
session.getRemote().sendString(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
rules file
rules file lookup order
1. configured through OSGI property
2. literal "rules.ini" file in parent dir of bundle
3. fall back to embedded rules
blocking (rules processed in order of occurence)
block requests matching a path pattern
block
pattern=robots.txt
allow all requests
allow
remapping external paths to internal paths
map favicon to /web/favicon.ico internally
map
path=favicon.ico
to=/web/favicon.ico
regex search and replace
map
pattern=/replaceme~
regex=^/replaceme/(.*)
to=/newpath/$1
patterns
specific path
pattern=robots.txt
separate multiple patterns with commas
pattern=*.php, *.gch
block admin and admin/*
pattern=admin~
osgi properties for configuring the framework (defaults after '=')
remote URL (used in redirects)
com.eriklievaart.jl.core.host=localhost:8000
com.eriklievaart.jl.core.https=false
a rules file can be used to block urls and to redirect requested paths (e.g. favicon) to different internal paths
com.eriklievaart.jl.core.rules=[defaults to preset rules]
redirect incoming requests on root to home path
com.eriklievaart.jl.core.servlet_prefix=
hide stack traces and render a custom error page instead, takes a redirect path (e.g. /oops)
com.eriklievaart.jl.core.exception.redirect=
specify directory containing freemarker templates (for hot deployment - real time editing during development)
com.eriklievaart.jl.freemarker.path=/${user.home}/Development/git/${project}/main/resources
specify timeout in milliseconds, 0 for immediate
com.eriklievaart.jl.freemarker.timeout=60000
email propertiescom.eriklievaart.jl.email.host=localhost
logging propertiescom.eriklievaart.osgi.appender.console=true
com.eriklievaart.osgi.appender.file=
com.eriklievaart.osgi.appender.file.level=TRACE
log to an osgi service
com.eriklievaart.osgi.appender.service=true
com.eriklievaart.toolkit.logging.date=
example logging format
com.eriklievaart.toolkit.logging.date=yyyy-MM-dd HH:mm:ss
antastic config
src/main/config/antastic.properties
src/main/config/dependencies.txt
jl-core @local @snapshot
jl-freemarker @local @snapshot
osgi-appender @local @snapshot
osgi-status @local @snapshot
osgi-toolkit @local @snapshot
toolkit-bean @local @snapshot
toolkit-convert @local @snapshot
toolkit-io @local @snapshot
toolkit-lang @local @snapshot
toolkit-logging @local @snapshot
toolkit-reflect @local @snapshot
toolkit-vfs @local @snapshot
freemarker org.freemarker 2.3.28
org.apache.felix.http.api org.apache.felix 3.0.0
org.apache.felix.http.jetty org.apache.felix 4.0.6
org.apache.felix.http.servlet-api org.apache.felix 1.1.2