Skip to content

Commit d45493d

Browse files
authored
Merge pull request #60 from jamezp/integration-updates
Update source files to use more modern approaches to creating instanc…
2 parents cdc6378 + 3443e41 commit d45493d

18 files changed

Lines changed: 457 additions & 329 deletions

README.adoc

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ You need to use the `GuiceResteasyBootstrapServletContextListener` as follows
6363
<servlet>
6464
<servlet-name>Resteasy</servlet-name>
6565
<servlet-class>
66-
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
66+
org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher
6767
</servlet-class>
6868
</servlet>
6969
@@ -79,31 +79,61 @@ You need to use the `GuiceResteasyBootstrapServletContextListener` as follows
7979
Also notice that there is a `resteasy.guice.modules` context-param.
8080
This can take a comma delimited list of class names that are Guice Modules.
8181

82+
== Registering resources and providers
83+
84+
Guice does not scan the classpath, so RESTEasy Guice can only see what your modules explicitly bind.
85+
Every `@Path` root resource and every `@Provider` must be bound in one of your modules; an unbound resource is simply not registered and its endpoints return `404`.
86+
87+
[source,java]
88+
----
89+
public class MyModule implements Module {
90+
@Override
91+
public void configure(final Binder binder) {
92+
binder.bind(HelloResource.class); // a @Path resource
93+
binder.bind(MyExceptionMapper.class); // an @Provider
94+
binder.bind(GreetingService.class).to(GreetingServiceImpl.class);
95+
}
96+
}
97+
----
98+
99+
Resources are instantiated by Guice, so constructor injection uses Guice bindings.
100+
This means the Jakarta REST `@Context` annotation cannot be used on a constructor parameter — RESTEasy performs `@Context` injection on fields and setters only, after Guice has created the instance.
101+
To inject the request-scoped context objects (`UriInfo`, `HttpHeaders`, etc.) through a constructor, install the `RequestScopeModule` (see below) and use a plain `@Inject` constructor.
102+
82103
== Request Scope
83104

84-
Add the `RequestScopeModule` to your modules to allow objects to be scoped to the HTTP request by adding the `@RequestScoped` annotation to your fields in resource classes.
85-
All the objects injectable via the `@Context` annotation are also injectable, except `ServletConfig` and `ServletContext`.
86-
Note that `RequestScopeModule` will already be added if any of your modules extends `com.google.inject.servlet.ServletModule`.
87-
In such cases you should not add it again to avoid injector creation errors.
105+
Add the `RequestScopeModule` to your modules to make the Jakarta REST context objects injectable with a plain Guice `@Inject`, bound to the current HTTP request.
106+
The following types are bound: `UriInfo`, `HttpHeaders`, `Request`, `SecurityContext`, `HttpServletRequest`, and `HttpServletResponse` (`ServletConfig` and `ServletContext` are not bound).
88107

89108
[source,java]
90109
----
91110
92111
import jakarta.inject.Inject;
93-
import jakarta.servlet.http.HttpServletRequest;
94-
import jakarta.ws.rs.core.Context;
112+
import jakarta.ws.rs.GET;
113+
import jakarta.ws.rs.Path;
114+
import jakarta.ws.rs.core.UriInfo;
115+
116+
@Path("example")
117+
public class ExampleResource {
118+
private final UriInfo uriInfo;
95119
96-
import dev.resteasy.guice.RequestScoped;
120+
@Inject
121+
public ExampleResource(final UriInfo uriInfo) {
122+
this.uriInfo = uriInfo;
123+
}
97124
98-
public class MyClass {
99-
@Inject @RequestScoped @Context
100-
private HttpRequest request;
125+
@GET
126+
public String get() {
127+
return uriInfo.getRequestUri().toString();
128+
}
101129
}
102130
----
103131

132+
The scope is also available directly as the `dev.resteasy.guice.RequestScoped` annotation for binding your own request-scoped objects.
133+
104134
== Binding Jakarta REST utilities
105135

106-
Add the `JaxrsModule` to bind `jakarta.ws.rs.ext.RuntimeDelegate`, `jakarta.ws.rs.core.Response.ResponseBuilder`, `jakarta.ws.rs.core.UriBuilder`, `jakarta.ws.rs.core.Variant.VariantListBuilder` and `org.jboss.resteasy.client.jaxrs.ClientHttpEngine`.
136+
Add the `JaxrsModule` to bind the Jakarta REST utility factories so they can be injected: `jakarta.ws.rs.ext.RuntimeDelegate`, `jakarta.ws.rs.core.Response.ResponseBuilder`, `jakarta.ws.rs.core.UriBuilder`, and `jakarta.ws.rs.core.Variant.VariantListBuilder`.
107137

108138
== Configuring Stage
109139

@@ -135,7 +165,7 @@ If this value is not specified, RESTEasy uses whatever Guice's default is.
135165
<servlet>
136166
<servlet-name>Resteasy</servlet-name>
137167
<servlet-class>
138-
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
168+
org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher
139169
</servlet-class>
140170
</servlet>
141171
@@ -235,13 +265,11 @@ Once everything is setup, you simply need to run the `./release.sh` script. Ther
235265

236266
By default the release version cannot contain `SNAPSHOT` and the development version, must container `SNAPSHOT`.
237267

238-
[source,bash]
239-
----
240268
.Example Command
269+
[source,bash]
241270
----
242271
./release -r 1.0.0.Final -d 1.0.1.Final-SNAPSHOT
243272
----
244-
----
245273

246274
=== Supported Arguments
247275

pom.xml

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,12 @@
7171
<!-- Check to see if it can be removed when updating Guice. -->
7272
<version.com.google.guava.guava>33.7.1-jre</version.com.google.guava.guava>
7373
<version.com.google.inject.guice>7.0.0</version.com.google.inject.guice>
74+
<version.org.eclipse.jetty>12.1.12</version.org.eclipse.jetty>
7475
<version.org.jboss.logging>3.6.3.Final</version.org.jboss.logging>
7576
<version.org.jboss.logging.jboss-logging-tools>3.0.4.Final</version.org.jboss.logging.jboss-logging-tools>
7677
<version.org.jboss.logmanager>3.2.2.Final</version.org.jboss.logmanager>
7778
<version.org.jboss.resteasy>7.0.3.Final</version.org.jboss.resteasy>
78-
<version.dev.resteasy.netty>1.0.0.Final</version.dev.resteasy.netty>
79+
<version.org.jboss.slf4j>2.1.0.Final</version.org.jboss.slf4j>
7980
<version.org.junit>6.1.3</version.org.junit>
8081

8182
<!-- Plugins versions -->
@@ -118,17 +119,18 @@
118119
<scope>import</scope>
119120
</dependency>
120121
<dependency>
121-
<groupId>dev.resteasy.netty</groupId>
122-
<artifactId>resteasy-netty-bom</artifactId>
123-
<version>${version.dev.resteasy.netty}</version>
122+
<groupId>org.eclipse.jetty</groupId>
123+
<artifactId>jetty-bom</artifactId>
124+
<version>${version.org.eclipse.jetty}</version>
124125
<type>pom</type>
125126
<scope>import</scope>
126127
</dependency>
127-
<!-- This is a typo in the BOM of the resteasy-netty-bom where there is no resteasy-netty-embedded-server -->
128128
<dependency>
129-
<groupId>dev.resteasy.netty</groupId>
130-
<artifactId>resteasy-embedded-server</artifactId>
131-
<version>${version.dev.resteasy.netty}</version>
129+
<groupId>org.eclipse.jetty.ee11</groupId>
130+
<artifactId>jetty-ee11-bom</artifactId>
131+
<version>${version.org.eclipse.jetty}</version>
132+
<type>pom</type>
133+
<scope>import</scope>
132134
</dependency>
133135
<dependency>
134136
<groupId>org.junit</groupId>
@@ -189,12 +191,14 @@
189191

190192
<!-- Test dependencies -->
191193
<dependency>
192-
<groupId>dev.resteasy.netty</groupId>
193-
<artifactId>resteasy-reactor-netty-client</artifactId>
194+
<groupId>org.eclipse.jetty</groupId>
195+
<artifactId>jetty-server</artifactId>
196+
<scope>test</scope>
194197
</dependency>
195198
<dependency>
196-
<groupId>dev.resteasy.netty</groupId>
197-
<artifactId>resteasy-embedded-server</artifactId>
199+
<groupId>org.eclipse.jetty.ee11</groupId>
200+
<artifactId>jetty-ee11-servlet</artifactId>
201+
<scope>test</scope>
198202
</dependency>
199203
<dependency>
200204
<groupId>org.junit.jupiter</groupId>
@@ -208,6 +212,12 @@
208212
<version>${version.org.jboss.logmanager}</version>
209213
<scope>test</scope>
210214
</dependency>
215+
<dependency>
216+
<groupId>org.jboss.slf4j</groupId>
217+
<artifactId>slf4j-jboss-logmanager</artifactId>
218+
<version>${version.org.jboss.slf4j}</version>
219+
<scope>test</scope>
220+
</dependency>
211221
</dependencies>
212222

213223
<build>

src/main/java/dev/resteasy/guice/GuiceResourceFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515

1616
import com.google.inject.Provider;
1717

18+
/**
19+
* A RESTEasy {@link ResourceFactory} backed by a Guice {@link Provider}. Each request obtains a fresh resource
20+
* instance from {@link Provider#get()} (so the resource's Guice scope governs its lifecycle) and then runs
21+
* RESTEasy property injection ({@code @Context} fields and setters) on it.
22+
*/
1823
public class GuiceResourceFactory implements ResourceFactory {
1924

2025
private final Provider<?> provider;
@@ -26,27 +31,31 @@ public GuiceResourceFactory(final Provider<?> provider, final Class<?> scannable
2631
this.scannableClass = scannableClass;
2732
}
2833

34+
@Override
2935
public Class<?> getScannableClass() {
3036
return scannableClass;
3137
}
3238

33-
public void registered(ResteasyProviderFactory factory) {
39+
@Override
40+
public void registered(final ResteasyProviderFactory factory) {
3441
propertyInjector = factory.getInjectorFactory().createPropertyInjector(scannableClass, factory);
3542
}
3643

3744
@Override
3845
public Object createResource(final HttpRequest request, final HttpResponse response,
3946
final ResteasyProviderFactory factory) {
4047
final Object resource = provider.get();
41-
CompletionStage<Void> propertyStage = propertyInjector.inject(request, response, resource, true);
48+
final CompletionStage<Void> propertyStage = propertyInjector.inject(request, response, resource, true);
4249
return propertyStage == null ? resource
4350
: propertyStage
4451
.thenApply(v -> resource);
4552
}
4653

54+
@Override
4755
public void requestFinished(final HttpRequest request, final HttpResponse response, final Object resource) {
4856
}
4957

58+
@Override
5059
public void unregistered() {
5160
}
5261
}

src/main/java/dev/resteasy/guice/GuiceResteasyBootstrapServletContextListener.java

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@
3131
import com.google.inject.Module;
3232
import com.google.inject.Stage;
3333

34+
/**
35+
* A {@link ServletContextListener} that bootstraps RESTEasy with Guice. Register it in {@code web.xml} (or an
36+
* equivalent programmatic servlet setup) alongside the RESTEasy dispatcher servlet.
37+
* <p>
38+
* On {@link #contextInitialized(ServletContextEvent) context initialization} it builds a Guice {@link Injector}
39+
* from the configured {@link Module}s and hands it to a {@link ModuleProcessor}, which registers every bound
40+
* {@code @Path} root resource and {@code @Provider} type with RESTEasy. Modules are, by default, taken from the
41+
* comma-separated {@code resteasy.guice.modules} context-param and instantiated with their no-arg constructor;
42+
* the Guice {@link Stage} may be set with the {@code resteasy.guice.stage} context-param.
43+
* <p>
44+
* The behavior can be customized by subclassing and overriding {@link #getModules(ServletContext)},
45+
* {@link #getStage(ServletContext)}, or {@link #withInjector(Injector)}; register the subclass as the listener.
46+
* If a parent {@link Injector} is available via {@link Inject field injection}, a child injector is created from
47+
* it instead. No-arg {@link PostConstruct} and {@link PreDestroy} methods on module instances are invoked on
48+
* context initialization and destruction, respectively.
49+
*/
3450
public class GuiceResteasyBootstrapServletContextListener extends ResteasyBootstrap implements ServletContextListener {
3551

3652
private List<? extends Module> modules;
@@ -71,18 +87,20 @@ public void contextInitialized(final ServletContextEvent event) {
7187
}
7288

7389
/**
74-
* Override this method to interact with the {@link Injector} after it has been created. The default is no-op.
90+
* Override this method to interact with the {@link Injector} after it has been created. The default is a no-op.
7591
*
76-
* @param injector
92+
* @param injector the fully-created injector for this deployment
7793
*/
7894
protected void withInjector(Injector injector) {
7995
}
8096

8197
/**
82-
* Override this method to set the Stage. By default it is taken from resteasy.guice.stage context param.
98+
* Override this method to set the Guice {@link Stage}. By default, it is taken from the
99+
* {@code resteasy.guice.stage} context-param, or {@code null} (Guice's own default) if that is not set.
100+
*
101+
* @param context the servlet context for this deployment
83102
*
84-
* @param context
85-
* @return Guice Stage
103+
* @return the Guice {@link Stage} to create the injector with, or {@code null} to use Guice's default
86104
*/
87105
protected Stage getStage(ServletContext context) {
88106
final String stageAsString = context.getInitParameter("resteasy.guice.stage");
@@ -97,27 +115,29 @@ protected Stage getStage(ServletContext context) {
97115
}
98116

99117
/**
100-
* Override this method to instantiate your {@link Module}s yourself.
118+
* Override this method to instantiate your {@link Module}s yourself, for example when a module needs
119+
* constructor arguments. The default reads the comma-separated {@code resteasy.guice.modules} context-param
120+
* and instantiates each listed class with its no-arg constructor.
121+
*
122+
* @param context the servlet context for this deployment
101123
*
102-
* @param context
103-
* @return
124+
* @return the modules to build the injector from; never {@code null}
104125
*/
105126
protected List<? extends Module> getModules(final ServletContext context) {
106-
final List<Module> result = new ArrayList<Module>();
127+
final List<Module> result = new ArrayList<>();
107128
final String modulesString = context.getInitParameter("resteasy.guice.modules");
108129
if (modulesString != null) {
109130
final String[] moduleStrings = modulesString.trim().split(",");
110131
for (final String moduleString : moduleStrings) {
111132
try {
112133
LogMessages.LOGGER.info(Messages.MESSAGES.foundModule(moduleString));
113134
final Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(moduleString.trim());
114-
final Module module = (Module) clazz.newInstance();
135+
final Module module = (Module) clazz.getDeclaredConstructor().newInstance();
115136
result.add(module);
116-
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
137+
} catch (ReflectiveOperationException e) {
117138
throw new RuntimeException(e);
118139
}
119140
}
120-
121141
}
122142
return result;
123143
}
@@ -128,9 +148,9 @@ public void contextDestroyed(final ServletContextEvent event) {
128148
}
129149

130150
private void triggerAnnotatedMethods(final Class<? extends Annotation> annotationClass) {
131-
for (Module module : this.modules) {
151+
for (final Module module : this.modules) {
132152
final Method[] methods = module.getClass().getMethods();
133-
for (Method method : methods) {
153+
for (final Method method : methods) {
134154
if (method.isAnnotationPresent(annotationClass)) {
135155
if (method.getParameterTypes().length > 0) {
136156
LogMessages.LOGGER.warn(Messages.MESSAGES.cannotExecute(module.getClass().getSimpleName(),
@@ -139,10 +159,7 @@ private void triggerAnnotatedMethods(final Class<? extends Annotation> annotatio
139159
}
140160
try {
141161
method.invoke(module);
142-
} catch (InvocationTargetException ex) {
143-
LogMessages.LOGGER
144-
.warn(Messages.MESSAGES.problemRunningAnnotationMethod(annotationClass.getSimpleName()), ex);
145-
} catch (IllegalAccessException ex) {
162+
} catch (InvocationTargetException | IllegalAccessException ex) {
146163
LogMessages.LOGGER
147164
.warn(Messages.MESSAGES.problemRunningAnnotationMethod(annotationClass.getSimpleName()), ex);
148165
}

src/main/java/dev/resteasy/guice/ModuleProcessor.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
import com.google.inject.Binding;
2222
import com.google.inject.Injector;
2323

24+
/**
25+
* Registers the Guice-managed Jakarta REST components of an {@link Injector} with RESTEasy. It walks the
26+
* injector's explicit bindings and, for each bound type, registers root resources (types recognized by
27+
* {@link GetRestful#isRootResource(Class)}) with the {@link Registry} and {@code @Provider}-annotated types with
28+
* the {@link ResteasyProviderFactory}. Because it scans only explicit bindings, every resource and provider must
29+
* be bound in a Guice {@link com.google.inject.Module}; Guice's just-in-time bindings are not discovered.
30+
*/
2431
public class ModuleProcessor {
2532

2633
private final Registry registry;
@@ -31,8 +38,13 @@ public ModuleProcessor(final Registry registry, final ResteasyProviderFactory pr
3138
this.providerFactory = providerFactory;
3239
}
3340

41+
/**
42+
* Registers the root resources and providers bound in the given injector with RESTEasy.
43+
*
44+
* @param injector the injector whose bindings should be registered
45+
*/
3446
public void processInjector(final Injector injector) {
35-
List<Binding<?>> rootResourceBindings = new ArrayList<Binding<?>>();
47+
final List<Binding<?>> rootResourceBindings = new ArrayList<>();
3648
for (final Binding<?> binding : injector.getBindings().values()) {
3749
final Class<?> type = binding.getKey().getTypeLiteral().getRawType();
3850
if (type != null) {
@@ -46,8 +58,8 @@ public void processInjector(final Injector injector) {
4658
}
4759
}
4860
}
49-
for (Binding<?> binding : rootResourceBindings) {
50-
Class<?> beanClass = (Class<?>) binding.getKey().getTypeLiteral().getType();
61+
for (final Binding<?> binding : rootResourceBindings) {
62+
final Class<?> beanClass = (Class<?>) binding.getKey().getTypeLiteral().getType();
5163
final ResourceFactory resourceFactory = new GuiceResourceFactory(binding.getProvider(), beanClass);
5264
LogMessages.LOGGER.info(Messages.MESSAGES.registeringFactory(beanClass.getName()));
5365
registry.addResourceFactory(resourceFactory);

src/main/java/dev/resteasy/guice/_private/Messages.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
@MessageBundle(projectCode = "RESTEASY-GUICE")
1515
public interface Messages {
1616
Messages MESSAGES = org.jboss.logging.Messages.getBundle(MethodHandles.lookup(), Messages.class);
17-
int BASE = 11000;
1817

1918
@Message(id = 100, value = "Cannot execute expected module {0}''s @{1} method {2} because it has unexpected parameters: skipping.", format = Format.MESSAGE_FORMAT)
2019
String cannotExecute(String className, String annotation, String methodName);

src/main/java/dev/resteasy/guice/ext/JaxrsModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
public class JaxrsModule implements Module {
2020

21+
@Override
2122
public void configure(final Binder binder) {
2223
binder.bind(ClientHttpEngine.class).to(ApacheHttpClient43Engine.class);
2324
binder.bind(RuntimeDelegate.class).toInstance(RuntimeDelegate.getInstance());

0 commit comments

Comments
 (0)