-
Notifications
You must be signed in to change notification settings - Fork 351
/
custom-di.xml
737 lines (598 loc) · 33 KB
/
custom-di.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
version 2 with the GNU Classpath Exception, which is available at
https://www.gnu.org/software/classpath/license.html.
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
-->
<!DOCTYPE chapter [<!ENTITY % ents SYSTEM "jersey.ent" > %ents;]>
<chapter xmlns="http://docbook.org/ns/docbook"
version="5.0"
xml:lang="en"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd
http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd"
xml:id="ioc">
<title>Custom Injection and Lifecycle Management</title>
<para>
Since version 2.0, Jersey uses &hk2.link; library for component life cycle management and dependency injection.
Rather than spending a lot of effort in maintaining Jersey specific API (as it used to be before Jersey 2.0 version),
Jersey defines several extension points where end-user application can directly manipulate Jersey HK2 bindings
using the HK2 public API to customize life cycle management and dependency injection of application components.
</para>
<para>
Jersey user guide can by no means supply an exhaustive documentation of HK2 API in its entire scope.
This chapter only points out the most common scenarios related
to dependency injection in Jersey and suggests possible options to implement these scenarios.
It is highly recommended to check out the &hk2.link; website and read HK2 documentation in order to get
better understanding of suggested approaches. HK2 documentation should also help in resolving use cases
that are not discussed in this writing.
</para>
<para>
There are typically three main use cases, where your application may consider dealing with
HK2 APIs exposed in Jersey:
<itemizedlist>
<listitem><simpara>Implementing a custom injection provider that allows an application to define
additional types to be injectable into Jersey-managed JAX-RS components.</simpara></listitem>
<listitem><simpara>Defining a custom injection annotation (other than &jee6.javax.inject.Inject;
or &jaxrs.core.Context;) to mark application injection points.</simpara></listitem>
<listitem><simpara>Specifying a custom component life cycle management for your application
components.</simpara></listitem>
</itemizedlist>
</para>
<para>
Since Jersey 2.26, the injection has been abstracted, so that &hk2.link; can be eventually replaced by the CDI or
any other injection framework. In the next chapters, we document possibilities provided directly by &hk2.link; and
by Jersey abstraction components.
</para>
<section xml:id="injection.manager">
<title>InjectionManager</title>
<para>
Since Jersey 2.26, Jersey comes with the main abstraction interface to communicate with the DI container, the
&jersey.common.internal.inject.InjectionManager;. What is <literal>ServiceLocator</literal> for &hk2.link;, or
<literal>BeanManager</literal> for CDI, that's &jersey.common.internal.inject.InjectionManager; for Jersey.
</para>
<para>
&jersey.common.internal.inject.InjectionManager; can be injected into the user provided classes instantiated
by Jersey. It can also be obtained programmatically by &jersey.client.InjectionManagerClientProvider;
and &lit.jersey.common.InjectionManagerProvider; from Jakarta REST components, such as
&jaxrs.core.FeatureContext;, or &jaxrs.ext.MessageBodyReader; and &jaxrs.ext.MessageBodyReader;.
</para>
<para>
Customers used to the &hk2.ServiceLocator; can still use it directly; the &hk2.ServiceLocator; can be obtained
either directly by injection, or programmatically as <literal>InjectionManager.getInstance(ServiceLocator.class)</literal>.
</para>
</section>
<section>
<title>Implementing Custom Injection Provider</title>
<para>
Relying on Servlet HTTP session concept is not very RESTful. It turns the originally state-less HTTP
communication schema into a state-full manner. However, it could serve
as a good example that will help me demonstrate implementation of the use cases described above.
The following examples should work on top of Jersey Servlet integration module. The approach that will be
demonstrated could be further generalized.
Below we will show how to make actual Servlet &jee6.servlet.HttpSession; injectable into JAX-RS components
and how to make this injection work with a custom inject annotation type. Finally, we will demonstrate
how you can write &lit.jee6.servlet.HttpSession;-scoped JAX-RS resources.
</para>
<para>
Jersey implementation allows you to directly inject &jee6.servlet.HttpServletRequest; instance into
your JAX-RS components.
It is quite straight forward to get the appropriate &lit.jee6.servlet.HttpSession; instance out of the
injected request instance.
Let say, you want to get &lit.jee6.servlet.HttpSession; instance directly injected into your JAX-RS
types like in the code snippet below.
<programlisting language="java">@Path("di-resource")
public class MyDiResource {
@Inject HttpSession httpSession;
...
}</programlisting>
</para>
<section>
<title>Using HK2 classes</title>
<para>
To make the above injection work, you will need to define an additional HK2 binding in your
application &jersey.server.ResourceConfig;.
Let's start with a custom HK2 &hk2.Factory; implementation that knows how to extract
&lit.jee6.servlet.HttpSession; out of given &lit.jee6.servlet.HttpServletRequest;.
<programlisting language="java">import org.glassfish.hk2.api.Factory;
...
public class HttpSessionFactory implements Factory<HttpSession> {
private final HttpServletRequest request;
@Inject
public HttpSessionFactory(HttpServletRequest request) {
this.request = request;
}
@Override
public HttpSession provide() {
return request.getSession();
}
@Override
public void dispose(HttpSession t) {
}
}</programlisting>
Please note that the factory implementation itself relies on having the actual
&lit.jee6.servlet.HttpServletRequest; instance injected.
In your implementation, you can of course depend on other types (and inject them conveniently)
as long as these other types are bound to the actual HK2 service locator by Jersey or by your
application. The key notion to remember here is that your HK2 &lit.hk2.Factory; implementation
is responsible for implementing the <literal>provide()</literal> method that is used by HK2
runtime to retrieve the injected instance. Those of you who worked with Guice binding API in the
past will most likely find this concept very familiar.
</para>
<para>
Once implemented, the factory can be used in a custom HK2 &lit.hk2.Binder; to define the
new injection binding for &lit.jee6.servlet.HttpSession;. Finally, the implemented binder
can be registered in your &jersey.server.ResourceConfig;:
<programlisting language="java">import org.glassfish.hk2.utilities.binding.AbstractBinder;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindFactory(HttpSessionFactory.class).to(HttpSession.class)
.proxy(true).proxyForSameScope(false).in(RequestScoped.class);
}
});
}
}</programlisting>
Note that if we did not define any explicit injection scope for the new injection binding,
By default, HK2 factories are bound in a HK2 &hk2.PerLookup; scope, which is in most
cases a good choice, and it is suitable also in our example.
</para>
<para>
To summarize the approach described above, here is a list of steps to follow
when implementing custom injection provider in your Jersey application :
<itemizedlist>
<listitem><simpara>Implement your own HK2 &lit.hk2.Factory; to provide the
injectable instances.</simpara></listitem>
<listitem><simpara>Use the HK2 &lit.hk2.Factory; to define an injection
binding for the injected instance via custom HK2 &lit.hk2.Binder;.</simpara></listitem>
<listitem><simpara>Register the custom HK2 &lit.hk2.Binder; in your application
&lit.jersey.server.ResourceConfig;.</simpara></listitem>
</itemizedlist>
</para>
<para>
While the &lit.hk2.Factory;-based approach is quite straight-forward and should help you to
quickly prototype or even implement final solutions, you should bear in mind, that your
implementation does not need to be based on factories. You can for instance bind your own
types directly, while still taking advantage of HK2 provided dependency injection.
Also, in your implementation you may want to pay more attention to defining or managing
injection binding scopes for the sake of performance or correctness of your custom injection
extension.
<important>
<para>
While the individual injection binding implementations vary and depend on your use case,
to enable your custom injection extension in Jersey, you must register your custom HK2 &hk2.Binder;
implementation in your application &jersey.server.ResourceConfig;!
</para>
</important>
</para>
</section>
<section>
<title>Injection Provider Using Jersey API</title>
<para>
To make the <literal>HttpSession</literal> injection work without using HK2 API,
we will need to create a custom supplier that knows how to extract
&lit.jee6.servlet.HttpSession; out of given &lit.jee6.servlet.HttpServletRequest;.
<programlisting language="java">import java.util.function.Supplier
...
public class HttpSessionSupplier implements Supplier<HttpSession> {
private final HttpServletRequest request;
@Inject
public HttpSessionSupplier(HttpServletRequest request) {
this.request = request;
}
@Override
public HttpSession get() {
return request.getSession();
}
}</programlisting>
Once implemented, the supplier can be used in a custom Jersey &jersey.common.internal.inject.AbstractBinder;
to define the new injection binding for &lit.jee6.servlet.HttpSession;. Finally, the implemented binder
can be registered in your &jersey.server.ResourceConfig;:
<programlisting language="java">import org.glassfish.jersey.internal.inject.AbstractBinder;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindFactory(HttpSessionSupplier.class).to(HttpSession.class)
.proxy(true).proxyForSameScope(false).in(RequestScoped.class);
}
});
}
}</programlisting>
The default scope for Jersey binder is similarly as for the HK2, the
&jersey.common.internal.inject.PerLookup;.
</para>
<para>
To summarize the approach described above, here is a list of steps to follow
when implementing custom injection provider in your Jersey application :
<itemizedlist>
<listitem><simpara>Implement your own Supplier to provide the
injectable instances.</simpara></listitem>
<listitem><simpara>Use the Supplier to define an injection
binding for the injected instance via custom &jersey.common.internal.inject.AbstractBinder;.
</simpara></listitem>
<listitem><simpara>Register the custom &jersey.common.internal.inject.AbstractBinder; in your application
&lit.jersey.server.ResourceConfig;.</simpara></listitem>
</itemizedlist>
<important>
<para>
Similarly to the HK2, to enable your custom injection extension in Jersey,
you must register your custom &jersey.common.internal.inject.AbstractBinder;
implementation in your application &jersey.server.ResourceConfig;!
</para>
</important>
</para>
</section>
</section>
<section>
<title>Defining Custom Injection Annotation</title>
<para>
Java annotations are a convenient way for attaching metadata to various elements of Java code.
Sometimes you may even decide to combine the metadata with additional functionality, such as
ability to automatically inject the instances based on the annotation-provided metadata.
The described scenario is one of the use cases where having means of defining a custom injection
annotation in your Jersey application may prove to be useful. Obviously, this use case applies also
to re-used existing, 3rd-party annotation types.
</para>
<para>
In the following example, we will describe how a custom injection annotation can be supported.
Let's start with defining a new custom <literal>SessionInject</literal> injection annotation
that we will specifically use to inject instances of &jee6.servlet.HttpSession;
(similarly to the previous example):
<programlisting language="java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SessionInject { }</programlisting>
The above <literal>@SessionInject</literal> annotation should be then used as follows:
<programlisting language="java">@Path("di-resource")
public class MyDiResource {
@SessionInject HttpSession httpSession;
...
}</programlisting>
Again, the semantics remains the same as in the example described in the previous section.
You want to have the actual HTTP Servlet session instance injected into your
<literal>MyDiResource</literal> instance. This time however, you expect that the
<literal>httpSession</literal> field to be injected must be annotated with
a custom <literal>@SessionInject</literal> annotation. Obviously, in this simplistic case
the use of a custom injection annotation is an overkill, however, the simplicity of the
use case will help us to avoid use case specific distractions and allow us better focus on
the important aspects of the job of defining a custom injection annotation.
</para>
<section>
<title>Custom Injection Annotation using HK2</title>
<para>
If you remember from the previous section, to make the injection in the code snippet above work,
you first need to implement the injection provider (HK2 &hk2.Factory;) as well as define the
injection binding for the &lit.jee6.servlet.HttpSession; type. That part we have already
done in the previous section.
We will now focus on what needs to be done to inform the HK2 runtime about our <literal>@SessionInject</literal>
annotation type that we want to support as a new injection point marker annotation. To do that,
we need to implement our own HK2 &hk2.InjectionResolver; for the annotation as demonstrated
in the following listing:
<programlisting language="java">import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
...
public class SessionInjectResolver implements InjectionResolver<SessionInject> {
@Inject
@Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver<Inject> systemInjectionResolver;
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (HttpSession.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, handle);
}
return null;
}
@Override
public boolean isConstructorParameterIndicator() {
return false;
}
@Override
public boolean isMethodParameterIndicator() {
return false;
}
}</programlisting>
The <literal>SessionInjectResolver</literal> above just delegates to the default
HK2 system injection resolver to do the actual work.
</para>
<para>
You again need to register your injection resolver with your Jersey application,
and you can do it the same was as in the previous case. Following listing includes
HK2 binder that registers both, the injection provider from the previous step
as well as the new HK2 inject resolver with Jersey application &lit.jersey.server.ResourceConfig;.
Note that in this case we're explicitly binding the <literal>SessionInjectResolver</literal>
to a &jee6.inject.Singleton; scope to avoid the unnecessary proliferation of
<literal>SessionInjectResolver</literal> instances in the application:
<programlisting language="java">import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import javax.inject.Singleton;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindFactory(HttpSessionFactory.class).to(HttpSession.class);
bind(SessionInjectResolver.class)
.to(new TypeLiteral<InjectionResolver<SessionInject>>(){})
.in(Singleton.class);
}
});
}
}</programlisting>
</para>
</section>
<section>
<title>Custom Injection Annotation using Jersey InjectionResolver</title>
<para>
Jersey also comes with its &jersey.common.internal.inject.InjectionResolver; used to translate into
the HK2 &hk2.InjectionResolver; during runtime. The abstraction is important for allowing to support
the custom injection annotation in various DI containers. For instance, the abstraction is used when
supporting injection using &jaxrs.core.Context; in the CDI container (<literal>jersey-cdi2-se</literal> module).
</para>
<para>
The SessionInjectResolver then looks as follows:
<programlisting language="java">import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.glassfish.jersey.internal.inject.InjectionResolver;
...
public class SessionInjectResolver implements InjectionResolver<SessionInject> {
private final InjectionManger injectionManager;
public SessionInjectResolver(InjectionManager) {
this.injectionManager = injectionManager;
}
@Override
public Object resolve(Injectee injectee) {
if (HttpSession.class == injectee.getRequiredType()) {
return injectionManager.getInstance(HttpServletRequest.class).getSession();
}
return null;
}
@Override
public boolean isConstructorParameterIndicator() {
return false;
}
@Override
public boolean isMethodParameterIndicator() {
return false;
}
@Override
public Class<SessionInject> getAnnotation() {
return SessionInject.class;
}
}</programlisting>
The SessionInjectResolver uses &jersey.common.internal.inject.InjectionManager; described in
<xref linkend="injection.manager"/>.
</para>
<para>
Unlike with &hk2.link;, Jersey &jersey.common.internal.inject.InjectionResolver; can only be bound
as instance in the &jersey.common.internal.inject.AbstractBinder;. That is why the
&jersey.common.internal.inject.InjectionManager; is used in the <literal>InjectionResolver</literal>
to resolve the <literal>HttpSession</literal> instance.
</para>
<para>
The &jersey.common.internal.inject.InjectionResolver; can be registered in the with Jersey application
&lit.jersey.server.ResourceConfig; as follows:
<programlisting language="java">import javax.ws.rs.core.Feature;
import org.glassfish.jersey.InjectionManagerProvider;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import javax.inject.Singleton;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(new Feature() {
@Override
public boolean configure(FeatureContext context) {
final InjectionManager injectionManager = InjectionManagerProvider.getInjectionManager(context);
context.register(new AbstractBinder() {
@Override
protected void configure() {
bind(new SessionInjectResolver(injectionManager)).to(HttpSession.class).in(Singleton.class);
}
});
return true;
}
});
}</programlisting>
</para>
</section>
</section>
<section>
<title>Custom Life Cycle Management</title>
<para>
The last use case discussed in this chapter will cover managing custom-scoped components
within a Jersey application.
If not configured otherwise, then all JAX-RS resources are by default managed on a per-request basis. A new instance
of given resource class will be created for each incoming request that should be handled by that resource class.
Let say you want to have your resource class managed in a per-session manner. It means a new instance of your
resource class should be created only when a new Servlet &jee6.servlet.HttpSession; is established.
(As with previous examples in the chapter, this example assumes the deployment of your application
to a Servlet container.)
</para>
<para>
Following is an example of such a resource class that builds on the support for
&lit.jee6.servlet.HttpSession; injection from the earlier examples described in this chapter.
The <literal>PerSessionResource</literal> class allows you to count the number of requests made within
a single client session and provides you a handy sub-resource method to obtain the number via
a HTTP &lit.http.GET; method call:
<programlisting language="java">@Path("session")
public class PerSessionResource {
@SessionInject HttpSession httpSession;
AtomicInteger counter = new AtomicInteger();
@GET
@Path("id")
public String getSession() {
counter.incrementAndGet();
return httpSession.getId();
}
@GET
@Path("count")
public int getSessionRequestCount() {
return counter.incrementAndGet();
}
}</programlisting>
Should the above resource be per-request scoped (default option), you would never be able to obtain
any other number but 1 from it's getReqs sub-resource method, because then for each request
a new instance of our <literal>PerSessionResource</literal> class would get created with a fresh
instance <literal>counter</literal> field set to 0.
The value of this field would get incremented to 1 in the the <literal>getSessionRequestCount</literal>
method before this value is returned.
In order to achieve what we want, we have to find a way how to bind the instances of
our <literal>PerSessionResource</literal> class to &lit.jee6.servlet.HttpSession; instances and
then reuse those bound instances whenever new request bound to the same HTTP client session arrives.
Let's see how to achieve this.
</para>
<para>
To get better control over your Jersey component instantiation and life cycle,
you need to implement a custom Jersey &jersey.server.spi.ComponentProvider; SPI,
that would manage your custom components.
Although it might seem quite complex to implement such a thing,
the component provider concept in Jersey is in fact very simple. It allows you to define
your own HK2 injection bindings for the types that you are interested in,
while informing the Jersey runtime at the same time that it should back out and leave
the component management to your provider in such a case.
By default, if there is no custom component provider found for any given component type, Jersey
runtime assumes the role of the default component provider and automatically defines the default
HK2 binding for the component type.
</para>
<para>
Following example shows a simple &lit.jersey.server.spi.ComponentProvider; implementation,
for our use case. Some comments on the code follow.
<programlisting language="java">import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
...
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.binding.BindingBuilderFactory;
import org.glassfish.jersey.server.spi.ComponentProvider;
@javax.ws.rs.ext.Provider
public class PerSessionComponentProvider implements ComponentProvider {
private ServiceLocator locator;
static class PerSessionFactory implements Factory<PerSessionResource>{
static ConcurrentHashMap<String, PerSessionResource> perSessionMap
= new ConcurrentHashMap<String, PerSessionResource>();
private final Provider<HttpServletRequest> requestProvider;
private final ServiceLocator locator;
@Inject
public PerSessionFactory(
Provider<HttpServletRequest> request,
ServiceLocator locator) {
this.requestProvider = request;
this.locator = locator;
}
@Override
@PerLookup
public PerSessionResource provide() {
final HttpSession session = requestProvider.get().getSession();
if (session.isNew()) {
PerSessionResource newInstance = createNewPerSessionResource();
perSessionMap.put(session.getId(), newInstance);
return newInstance;
} else {
return perSessionMap.get(session.getId());
}
}
@Override
public void dispose(PerSessionResource r) {
}
private PerSessionResource createNewPerSessionResource() {
final PerSessionResource perSessionResource = new PerSessionResource();
locator.inject(perSessionResource);
return perSessionResource;
}
}
@Override
public void initialize(ServiceLocator locator) {
this.locator = locator;
}
@Override
public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
if (component == PerSessionResource.class) {
final DynamicConfigurationService dynamicConfigService =
locator.getService(DynamicConfigurationService.class);
final DynamicConfiguration dynamicConfiguration =
dynamicConfigService.createDynamicConfiguration();
BindingBuilderFactory
.addBinding(BindingBuilderFactory.newFactoryBinder(PerSessionFactory.class)
.to(PerSessionResource.class), dynamicConfiguration);
dynamicConfiguration.commit();
return true;
}
return false;
}
@Override
public void done() {
}
}</programlisting>
</para>
<para>
The first and very important aspect of writing your own &lit.jersey.server.spi.ComponentProvider;
in Jersey is to store the actual HK2 &hk2.ServiceLocator; instance that will be passed to you as
the only argument of the provider <literal>initialize</literal> method.
Your component provider instance will not get injected at all so this is more or less your only chance
to get access to the HK2 runtime of your application. Please bear in mind, that at the time when
your component provider methods get invoked, the &lit.hk2.ServiceLocator; is not fully configured yet.
This limitation applies to all component provider methods, as the main goal of any component provider
is to take part in configuring the application's &lit.hk2.ServiceLocator;.
</para>
<para>
Now let's examine the <literal>bind</literal> method, which is where your provider tells the HK2
how to bind your component.
Jersey will invoke this method multiple times, once for each type that is registered with the
actual application.
Every time the <literal>bind</literal> method is invoked, your component provider needs to decide
if it is taking control over the component or not. In our case we know exactly which Java type
we are interested in (<literal>PerSessionResource</literal> class),
so the logic in our <literal>bind</literal> method is quite straightforward. If we see our
<literal>PerSessionResource</literal> class it is our turn to provide our custom binding for the class,
otherwise we just return false to make Jersey poll other providers and, if no provider kicks in,
eventually provide the default HK2 binding for the component.
Please, refer to the &hk2.link; documentation for the details of the concrete HK2 APIs used in
the <literal>bind</literal> method implementation above. The main idea behind the code is that
we register a new HK2 &hk2.Factory; (<literal>PerSessionFactory</literal>), to provide
the <literal>PerSessionResource</literal> instances to HK2.
</para>
<para>
The implementation of the <literal>PerSessionFactory</literal> is also included above.
Please note that as opposed to a component provider implementation that should never itself rely
on an injection support, the factory bound by our component provider would get injected just fine,
since it is only instantiated later, once the Jersey runtime for the application is fully
initialized including the fully configured HK2 runtime.
Whenever a new session is seen, the factory instantiates and injects
a new PerSessionResource instance. The instance is then stored in the perSessionMap for later use
(for future calls).
</para>
<para>
In a real life scenario, you would want to pay more attention to possible synchronization issues.
Also, we do not consider a mechanism that would clean-up any obsolete resources for closed, expired or
otherwise invalidated HTTP client sessions.
We have omitted those considerations here for the sake of brevity of our example.
</para>
</section>
</chapter>