Index

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 properties
com.eriklievaart.jl.email.host=localhost
logging properties
com.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