Skip to main content

Spring Boot / Spring Framework

Spring Framework​

Das Spring Framework hat sich in den letzten Jahren bei der schnellen Entwicklung von Java Backend Anwendungen etabliert. Spring macht es den Entwicklern leicht, produktionsreife Anwendungen in kurzer Zeit zu erstellen. Das Spring Framework ergänzt dabei die bekannten JakartaEE Spezifikationen wie zum Beispiel Servlet API (JSR 340), WebSocket API (JSR 356), JSON Binding API (JSR 367) oder JPA (JSR 338).

Das Spring Framework basiert auf einem objektorientierten Entwurfsmuster, das als Dependency Injection bekannt geworden ist. Hierbei geht es darum, Objekte möglichst lose miteinander zu koppeln. Dies wird dadurch erreicht, dass ein Objekt andere Objekte, deren Hilfe es für seine Aufgabe benötigt, von außen "injiziert" bekommt, anstatt diese selbst zu erzeugen oder sich auf andere Art und Weise eigenverantwortlich zu beschaffen.

Spring Boot​

Spring Boot hilft bei der Entwicklung von Spring basierten stand-alone Anwendungen, was sehr gut in die Welt von Microservices passt. Das jadice web toolkit und Spring Boot geben dabei einen Weg vor, der unserer Ansicht nach am besten funktioniert, um möglichst schnell und einfach eine Anwendung zu entwickeln. Dies wird unter anderem dadurch erzielt, dass es sinnvolle Defaults gibt und möglichst wenig Konfiguration erforderlich ist, um eine Anwendung zu entwickeln.

Integratoren, die das jadice web toolkit in eine Spring- oder Spring-Boot-Applikation integrieren möchten, können das ausgelieferte Integrationsmodul nutzen.

Spring Boot-Integrationsmodul​

Das jadice web toolkit folgt dem Spring-Boot-Starter-Konzept, so, dass das Aufsetzen einer Spring-Boot-Anwendung mit dem jadice web toolkit sich sehr einfach gestaltet. Das webtoolkit-spring-boot-starter als Starter für das jadice web toolkit ist Bestandteil der Auslieferung und kann als Maven-Dependency eingebunden werden. Er bringt die erforderlichen Dependencies auf die einzubindenden jadice-web-toolkit- und Spring-Boot-Module mit:

    <dependency>
<groupId>com.levigo.jadice.webtoolkit</groupId>
<artifactId>webtoolkit-spring-boot-starter</artifactId>
</dependency>

Ein einführendes Tutorial, basierend auf Spring Boot, existiert unter Getting Started - jadice web toolkit mit Spring Boot.

Der Grundgedanke des webtoolkit-spring-boot-starter liegt darin, die Spring-Paradigmen für das jadice web toolkit zu unterstützen, so, dass die erforderlichen Komponenten deklarativ injiziert bzw. konfiguriert werden können. Dies umfasst DocumentDataProvider, Message-Listener, ContextualFactories sowie Annotationsprofile und Konfigurationswerte - wie nachfolgend ausführlich beschrieben. Die programmatische Registrierung dieser Komponenten, die früher typischerweise über einen WebtoolkitServletContextListener vorgenommen wurde, kann dadurch vollständig entfallen.

Spring Boot Dependencies​

Das jadice web toolkit erwartet, dass die Spring Boot Dependencies von der Integration bereitgestellt werden. Dadurch haben Sie die Kontrolle, über die konkret eingebundene Version. Wir empfehlen, die BOM (Bill of Materials) zu nutzen, um die Versionen und Abhängigkeiten zu setzen. Weitere Informationen hierzu finden Sie in unserer Knowledge Base.

Threadmanagement​

Das Spring-Modul sorgt dafür, dass bei asynchron ausgeführten Operationen der zuständige Thread mit dem Spring-SecurityContext assoziiert wird. Dies passiert allerdings nur, sofern Spring Security im Klassenpfad gefunden wird. Andernfalls wird ein gewöhnlicher ThreadPoolExecutor aufgerufen.

Spring Boot Application​

Eine jadice web toolkit-Spring-Boot-Anwendung wird durch Hinzufügen der Annotationen @SpringBootApplication und @EnableJWTSpringBootApplication(...) definiert:

  • @SpringBootApplication: zur Aktivierung einer Spring Boot Application, entsprechend Using the @SpringBootApplication Annotation

  • @EnableJWTSpringBootApplication(...): Diese Annotation bringt der webtoolkit-spring-boot-starter mit. Sie aktiviert den Component-Scan über das Package com.levigo.jadice.web und sorgt im Hintergrund für ein Autowiring von DocumentDataProvidern, Message-Listenern und Annotationsprofilen.

    @SpringBootApplication
// Activate jadice web toolkit support specifying the GWT module name for the entry point
@EnableJWTSpringBootApplication("com.levigo.jadice.web.demo.springboot.Application")
public class DemoApplication extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

Automatische Registrierung von DocumentDataProvidern​

DocumentDataProvider-Implementierungen lassen sich mittels Dependency Injection automatisch registrieren. Eine programmatische Registrierung an der DocumentDataProviderRegistry (siehe DocumentDataProviderRegistry) ist dann nicht mehr erforderlich. Hierzu ist die entsprechende Klasse lediglich mit der Spring-Annotation @Component zu versehen:

    @Component
public class MyDocumentDataProvider implements DocumentDataProvider<MySource, MyPageSegmentHandle> {
...
}

Automatische Registrierung von Message-Listenern​

Auch Message-Listener lassen sich als @Component injizieren. Der Nachrichtenname wird der Annotation @MessageName entnommen, eine manuelle Registrierung über JWTServerContext#registerMessageListeners(List) lt. Message-Listener entfällt dann.

    @Component
@MessageName("CUSTOM")
public class CustomMessageListener implements MessageListener<CustomRequestDTO> {
...
}

Zu beachten ist, dass pro Nachrichtenname nur ein Listener registriert werden kann. Soll ein vom jadice web toolkit mitgelieferter Listener angepasst werden — etwa der ExportOperationMessageListener — muss dessen Bean ersetzt und nicht zusätzlich registriert werden, da die zweite Registrierung desselben Nachrichtennamens mit einer IllegalArgumentException abgelehnt wird.

Automatische Registrierung von ContextualFactories​

Genauso können auch ContextualFactories als @Component injiziert werden, ohne dass diese programmatisch registriert werden müssen. Sie werden für die kontextabhängige Erzeugung von DocumentDataProvidern verwendet:

    @Component
public class MyContextualDocumentDataProviderFactory
implements ContextualFactory<DocumentDataProvider<ClassPathSource, ClassPathHandle>> {
@Override
public DocumentDataProvider<ClassPathSource, ClassPathHandle> create(InvocationContext context) {
return new ClassPathDocumentDataProvider();
}
}

@Component
public class MyHttpSessionAwareFactory
implements ContextualFactory<DocumentDataProvider<MySource, MyHandle>> {
@Override
public DocumentDataProvider<MySource, MyHandle> create(InvocationContext context) {
ServletInvocationContext servletInvocationContext = (ServletInvocationContext) context;
return new MyDocumentDataProvider(servletInvocationContext.getSession().getId());
}
}

Konfiguration serverseitiger Einstellungen​

In einer Spring-Boot-Umgebung lassen sich Einstellungen des jadice web toolkit, die für gewöhnlich programmatisch über die ServerConfiguration vorgenommen werden, deklarativ über eine application.yml bzw. application.properties konfigurieren.

Dabei werden die für das jadice web toolkit spezifischen Einstellungen mit dem Prefix webtoolkit angesprochen und die Werte über entsprechende Selektoren konfiguriert. Die Namen der Selektoren entsprechen dabei den Namen der Setter der ServerConfiguration.

Im folgenden Beispiel wird eine programmatische Konfiguration ihrem deklarativen Pendant gegenübergestellt:

ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setSessionTimeout(Duration.ofSeconds(30));
ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setResponseAggregationWindow(Duration.ofMillis(20));
ConfigurationManager.getServerConfiguration().setTileCompressionType(TileCompressionType.PNGJ_BEST_COMPRESSION);
ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setKeepAliveInterval(Duration.ofSeconds(30));

Konfiguration per application.properties:

webtoolkit.tileCompressionType: PNGJ_BEST_COMPRESSION
webtoolkit.networkConfiguration.sessionTimeout: 30s
webtoolkit.networkConfiguration.responseAggregationWindow: 20ms
webtoolkit.networkConfiguration.keepAliveInterval: 30s

Konfiguration per application.yml:

webtoolkit:
tileCompressionType: PNGJ_BEST_COMPRESSION

networkConfiguration:
sessionTimeout: 30s
responseAggregationWindow: 20ms
keepAliveInterval: 30s

Deklaration von Annotationsprofilen​

Neben den gerade erwähnten Konfigurationswerten lassen sich auch Annotationsprofile über die Spring-Boot-Konfigurationsdateien spezifizieren. Nachfolgendes Beispiel zeigt die Konfiguration zweier Annotationsprofile in einer application.yml.

#JWT prefix
webtoolkit:

# Specify annotation profiles
annotationProfiles: /jwt-minimal-profile.xml, /jwt-second-profile.xml

# Specify default annotation profile (The value must match annotation-profile name in jwt-minimal-profile.xml)
defaultAnnotationProfile: JWT-Minimal

WAR-Deployment (Traditional Deployment)​

Spring Boot Anwendungen sind dafür vorgesehen, als "Runnable-JAR" erstellt zu werden, das heißt, nach dem Build der Anwendung entsteht eine JAR-Datei, die direkt mit java -jar MySpringBootApp.jar gestartet werden kann. Spring Boot liefert hierfür einen eingebetteten Tomcat mit. Durch den Import eines anderen "Starter"-Moduls können beispielsweise auch Wildfly oder weitere App-Server verwendet werden.

Es ist jedoch auch weiterhin möglich, die Anwendung als WAR-Datei erstellen zu lassen, um sie so auf einem App-Server zu deployen. Die dafür benötigten Anpassungen an den Dependencies finden Sie im Kapitel Artefakte.

Möglicherweise sind darüber hinaus noch Anpassungen am Code notwendig. Beim Traditional Deployment wird der App-Server nicht von Spring Boot gestartet und verwaltet. Das hat zur Folge, dass manche Dinge anders funktionieren als erwartet. Insbesondere das Verhalten von @ServletComponentScan ist in diesem Szenario anders. Da der App-Server die Javax-Annotationen wie @WebServlet, @WebFilter etc. erfasst und die Klassen entsprechend registriert, kann Spring Boot hier nicht eingreifen und Felder die per @Autowired annotiert sind folglich nicht automatisch injecten. Wenn Sie also beispielsweise in Servlets Komponenten per Dependency Injection (@Autowired) injizieren möchten, funktioniert dies beim Traditional Deployment nicht.

Die folgenden Schritte sind nötig, um das Autowiring manuell durchzuführen:

  • @WebServlet-Annotation im Servlet entfernen
  • An einer Klasse, welche vom @ComponentScan erfasst wird (also in einem entsprechendem Java-Package liegt), das folgende Feld injecten: @Autowired AutowireCapableBeanFactory beanFactory;
  • Ein Snippet entsprechend dem Beispiel in dieser Klasse einfügen
@Bean
public ServletRegistrationBean<MyTestServlet> fileUploadServiceServletRegistrationBean(){
ServletRegistrationBean<MyTestServlet> servletRegistrationBean = new ServletRegistrationBean<>();
MyTestServlet servlet = new MyTestServlet();
beanFactory.autowireBean(servlet);
servletRegistrationBean.setServlet(servlet);
servletRegistrationBean.setUrlMappings(Collections.singletonList("/testEndpoint"));
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}

Eine weitere Besonderheit in diesem Szenario ist, dass die web.xml-Datei nicht entfallen darf.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<display-name>MyApp</display-name>
</web-app>

Beim Traditional Deployment kann außerdem die Verwendung des spring-boot-maven-plugin entfallen. Dieses Plugin sorgt dafür, dass durch das Repacking zwei Ordner entstehen, WEB-INF/lib und WEB-INF/lib-provided. Beim Deployment auf einem App-Server, werden die Libraries unter WEB-INF/lib-provided ignoriert. Wird die Anwendung jedoch über die Command-Line gestartet (java -jar), werden die Libraries verwendet, da in diesem Fall kein App-Server zur Verfügung steht. Wenn Sie sich für das Traditional Deployment entschieden haben, ist der Ordner WEB-INF/lib-provided in jedem Fall überflüssig und kann somit entfallen, da die Dateigröße des WAR-Archivs nur unnötig ansteigt. Weitere Informationen hierzu finden Sie in der offiziellen Dokumentation.

Nutzung des jadice web toolkit ohne Spring Framework​

Das jadice web toolkit kann grundsätzlich auch ohne das Spring Framework und den damit verbundenen Mechanismen wie Dependency Injection genutzt werden. Dies wird jedoch ausdrücklich nicht empfohlen.

Hierfür empfiehlt es sich, den Weg zu wählen, der in der Vergangenheit vom jadice web toolkit verwendet wurde: eine Konfiguration über den WebtoolkitServletContextListener. Dieser ist inzwischen als @Deprecated markiert. Eine lauffähige Referenz existiert dennoch weiterhin: Der Showcase, online erreichbar unter https://webtoolkit.jadice.com/showcase/, wird bewusst ohne Spring Boot betrieben und als klassisches WAR auf einem eigenständigen Tomcat ausgeliefert. Zu beachten ist, dass auch in diesem Fall die Spring-Bibliotheken im Classpath verbleiben müssen, da der JWTServerContext selbst auf Spring-Typen aufsetzt. Verzichtet wird lediglich auf Spring Boot und die Dependency Injection. Da ohne Dependency Injection die benötigten Services nicht automatisch erstellt werden, ist dies von Hand zu erledigen. Die Schritte hierfür sind nicht trivial:

  • Erstellen eines JWTServerContext. Dieser enthält die Services, die vom jadice web toolkit benötigt werden. Der Context kann dann im ServletContext des Application Servers abgelegt werden, damit auf jadice Services, wie zum Beispiel den TileRenderService, an anderen Stellen (Servlets etc.) zugegriffen werden kann.
  • Konfiguration der jadice document platform über JadicePreferenceHolder
  • Initialisierung der Caches über CacheManager
  • Initialisierung der Fonts über FontEnvironments
  • Initialisierung der Thread-Pools
  • Initialisierung des TransportManagers
  • Registrierung der Message-Listener über JWTServerContext#registerMessageListeners(List) (siehe Message-Listener); alternativ direkte Registrierung einzelner Listener über ServerMessageManager.get().registerMessage(name, descriptor), ohne den Umweg über die @MessageName-Auswertung
  • Registrierung der mitgelieferten Standardnachrichten über ServerMessageManager.get().registerDefaultMessages() — hierüber wird u.a. das Abbrechen laufender Konversationen registriert
  • Registrierung der Mappings für eigene Source- und PageSegmentHandle-Typen an SourceMapper und PageSegmentHandleMapper
  • Erweitern des TileServlets für URL-Mapping
  • Registrierung der DocumentDataProvider
  • Registrierung des Annotationsprofils

Beispiel​

Das folgende Beispiel zeigt eine solche manuelle Initialisierung in vereinfachter Form. Es dient als Orientierung und ist an die jeweilige Integration anzupassen.

WebtoolkitServletContextListener​

@Override
protected void contextInitialized(ServletContextEvent sce, WebtoolkitServerContext context) {
JWTServerContext jwtServerContext = new JWTServerContext();
JadicePreferenceHolder.getInstance();
final PreferenceStore ps = PreferenceStoreHolder.getPreferenceStoreByName(
JadicePreferenceHolder.JADICE_CONFIGURATION);
JadicePropertiesConfiguration.configure(ps);
CacheManager.setDefault(DefaultCacheProvider.getDefaultServerCache());
GitInfo.logCommitIdOnce();
FontEnvironments.initialize();
DocumentDataProviderRegistry documentDataProviderRegistry = new DocumentDataProviderRegistryImpl();
jwtServerContext.setDocumentDataProviderRegistry(documentDataProviderRegistry);
ServerConfiguration serverConfiguration = ConfigurationManager.getServerConfiguration();
ThreadFactory threadFactory = new ThreadFactory() {
final ThreadGroup threadGroup = new ThreadGroup("JadiceWebToolkit General Threadpool");
final AtomicInteger counter = new AtomicInteger();

public Thread newThread(final Runnable r) {
Thread t = new Thread(this.threadGroup, r, this.createThreadName(this.counter.incrementAndGet()));
t.setPriority(5);
return t;
}

private String createThreadName(final int threadNumber) {
return "JadiceWebToolkit General Worker-" + threadNumber;
}
};
final JWTThreadPoolExecutor threadPoolExecutor = new JWTThreadPoolExecutor(
serverConfiguration.getGeneralPoolCoreSize(),
serverConfiguration.getGeneralPoolMaxSize(),
15,
TimeUnit.SECONDS,
threadFactory
);
jwtServerContext.setExecutorService(threadPoolExecutor);
AnnotationService annotationService = new AnnotationService();
jwtServerContext.setAnnotationService(annotationService);
TileRenderPriorityTaskExecutor tileRenderPriorityTaskExecutor = new TileRenderPriorityTaskExecutor(threadFactory);
TileRenderer tileRenderer = new TileRenderer(tileRenderPriorityTaskExecutor);
DocumentService documentService = new DocumentService(tileRenderer, threadPoolExecutor,
documentDataProviderRegistry, annotationService);
jwtServerContext.setDocumentService(documentService);
TileRenderService tileRenderService = new TileRenderService(documentService, tileRenderPriorityTaskExecutor, tileRenderer);
jwtServerContext.setTileRenderService(tileRenderService);

DocumentSnapshotConverter documentSnapshotConverter = new DocumentSnapshotConverter(documentService);
TextService textService = new TextService(threadPoolExecutor, documentService, documentSnapshotConverter);
jwtServerContext.setTextService(textService);

// Initialize the TransportManager
final TransportManager transportManager = TransportManagerProvider.getFromServletContext(sce.getServletContext());
final TransportManagerImpl transportManagerImpl = (TransportManagerImpl) transportManager;
transportManagerImpl.init(ConfigurationManager.getServerConfiguration().getNetworkConfiguration());

// Optional: Export
ExportRepository exportRepository = ExportRepository.get(sce.getServletContext());
ExportOperationMessageListener exportOperationMessageListener = new ExportOperationMessageListener();
exportOperationMessageListener.setRepository(exportRepository);
exportOperationMessageListener.setExecutorService(threadPoolExecutor);
exportOperationMessageListener.setSnapshotConverter(documentSnapshotConverter);

// Register MessageListeners
List<MessageListener<?>> messageListeners = new ArrayList<>();

CancelTileServletMessageListener cancelTileServletMessageListener = new CancelTileServletMessageListener();
cancelTileServletMessageListener.setTileRenderService(tileRenderService);
messageListeners.add(cancelTileServletMessageListener);

CreateDocumentMessageListener createDocumentMessageListener = new CreateDocumentMessageListener();
createDocumentMessageListener.setDocumentService(documentService);
messageListeners.add(createDocumentMessageListener);

GetAnnotationProfileMessageListener getAnnotationProfileMessageListener = new GetAnnotationProfileMessageListener();
getAnnotationProfileMessageListener.setAnnotationService(annotationService);
messageListeners.add(getAnnotationProfileMessageListener);

GetInstructionsMessageListener getInstructionsMessageListener = new GetInstructionsMessageListener();
getInstructionsMessageListener.setExecutorService(threadPoolExecutor);
getInstructionsMessageListener.setSnapshotConverter(documentSnapshotConverter);
messageListeners.add(getInstructionsMessageListener);

GetTextContentMessageListener getTextContentMessageListener = new GetTextContentMessageListener();
getTextContentMessageListener.setTextService(textService);
getTextContentMessageListener.setExecutorService(threadPoolExecutor);
messageListeners.add(getTextContentMessageListener);

RemoteLoggingMessageListener remoteLoggingMessageListener = new RemoteLoggingMessageListener();
remoteLoggingMessageListener.setExecutorService(threadPoolExecutor);
messageListeners.add(remoteLoggingMessageListener);

TextSearchRequestMessageListener textSearchRequestMessageListener = new TextSearchRequestMessageListener();
textSearchRequestMessageListener.setTextService(textService);
messageListeners.add(textSearchRequestMessageListener);

messageListeners.add(exportOperationMessageListener);
// Attention: registerMessageListeners() resets the ServerMessageManager. Therefore it has
// to be called before registerDefaultMessages() and before any registerMessage() call.
jwtServerContext.registerMessageListeners(messageListeners);

// Register the default messages shipped with the jadice web toolkit
ServerMessageManager.get().registerDefaultMessages();

// Register Source-/PageSegmentHandle-Mappings for own types
final ObjectMapper objectMapper = new ObjectMapper();
SourceMapper.get().registerDirectMapping(ClassPathWithAnnoSource.TYPE, ClassPathWithAnnoSource.class);
PageSegmentHandleMapper.get().registerMapping(ClassPathWithAnnoSource.TYPE, ClassPathWithAnnoHandle.class,
(handle) -> objectMapper.valueToTree(ClassPathWithAnnoParamsDTO.from((ClassPathWithAnnoHandle) handle)),
(json) -> ClassPathWithAnnoParamsDTO.toHandle(objectMapper.treeToValue(json, ClassPathWithAnnoParamsDTO.class)));

// Register DataProvider
documentDataProviderRegistry.registerProvider(ClassPathWithAnnoSource.class, //
ClassPathWithAnnoHandle.class, //
new ClassPathWithAnnoDocumentDataProvider() //
);

// Register Anno Profiles
AnnotationProfile annotationProfile = AnnotationProfile.load(getClass().getResource("/annotationConfigurations/jwt-annotation-profile.xml"));
annotationService.registerProfile(annotationProfile);

// as we're dealing with a single profile only, it will be our default profile.
AnnotationProfile.setDefaultProfile(annotationProfile);

// Place JWTServerContext in ServletContext so it can be retrieved from Servlets, e.g. a custom TileServlet
sce.getServletContext().setAttribute("JWT_SERVER_CONTEXT", jwtServerContext);
}

Die Liste der Message-Listener muss alle Funktionen abdecken, die in der Anwendung genutzt werden. Bei einer Spring-Boot-Integration werden dagegen alle MessageListener-Beans automatisch registriert — darunter auch Listener, die im Beispiel oben nicht enthalten sind, etwa der AuthenticationInfoUpdateHandler (Nachricht AUTHENTICATION_INFO) und der AdditionalHTTPHeaderHandler (Nachricht ADDITIONAL_HTTP_HEADERS).

Ohne Spring werden die Nachrichten nicht automatisch registriert. Zu beachten ist dabei die Reihenfolge: registerMessageListeners(List) ruft intern ServerMessageManager#reset() auf und verwirft damit alle bereits registrierten Nachrichten. Zuerst also registerMessageListeners(List) aufrufen, danach registerDefaultMessages() sowie eigene Registrierungen über ServerMessageManager.get().registerMessage(name, descriptor). Eine zweite Registrierung desselben Nachrichtennamens wird mit einer IllegalArgumentException abgelehnt.

Die DocumentDataProvider werden — wie im Beispiel oben — an der DocumentDataProviderRegistry registriert; die Registrierung selbst unterscheidet sich nicht zwischen den Umgebungen (siehe DocumentDataProviderRegistry).

Zusätzlich sind für jeden eigenen Source- bzw. PageSegmentHandle-Typ die Mappings an SourceMapper und PageSegmentHandleMapper zu registrieren, da diese nicht automatisch ermittelt werden (siehe Laden von Dokumenten, Abschnitt „Laden über Source/Handle").

In einer Integration ohne Spring Boot und das dazugehörende DependencyInjection kann der JWTServerContext über den folgenden Mechanismus zur Verfügung gestellt werden.

MyTileServlet​

@WebServlet(
asyncSupported = true,
description = "Servlet handling tile requests",
displayName = "jadice web toolkit tile download",
name = "jwtTileDownloadServlet",
urlPatterns = {
"/jwt/tile/*"
})
public class MyTileServlet extends TileServlet {

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
if (null == tileRenderService) {
final JWTServerContext jwtServerContext = (JWTServerContext) getFromServletContext(
config.getServletContext());
if (null == jwtServerContext)
throw new UnavailableException("No JWTServerContext found in context");

tileRenderService = jwtServerContext.getTileRenderService();
}
}

public WebtoolkitServerContext getFromServletContext(ServletContext servletContext) {
JWTServerContext serverContext = (JWTServerContext) servletContext.getAttribute("JWT_SERVER_CONTEXT");

if (serverContext == null) {
throw new NullPointerException("JWTServerContext was not placed in ServletContext");
}
return serverContext;
}
}