This is an automated email from the ASF dual-hosted git repository. jamesbognar pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push: new ac54809 REST refactoring. ac54809 is described below commit ac548099580d30ddb95e4e4f559522c0472a1313 Author: JamesBognar <james.bog...@salesforce.com> AuthorDate: Sun Jan 24 11:58:08 2021 -0500 REST refactoring. --- .../org/apache/juneau/cp/BeanFactory_Test.java | 4 +- .../java/org/apache/juneau/cp/BeanFactory.java | 10 + .../build-overlay/pom.xml | 18 +- .../build-overlay/pom.xml | 4 +- .../juneau-examples-rest-springboot/pom.xml | 4 +- .../apache/juneau/rest/mock/MockRestClient.java | 2 +- juneau-rest/juneau-rest-server-springboot/pom.xml | 2 +- .../rest/annotation/RestAnnotation_Test.java | 27 +- .../rest/annotation/RestMethodAnnotation_Test.java | 10 +- .../juneau/rest/annotation/Rest_Context_Test.java | 6 +- .../juneau/rest/annotation/Restx_Path_Test.java | 6 +- .../main/java/org/apache/juneau/rest/RestCall.java | 2 +- ...RestMethodsBuilder.java => RestChildMatch.java} | 60 +-- .../java/org/apache/juneau/rest/RestChildren.java | 125 ++++++ .../apache/juneau/rest/RestChildrenBuilder.java | 92 +++++ .../java/org/apache/juneau/rest/RestContext.java | 450 ++++++++++++++++----- .../org/apache/juneau/rest/RestContextBuilder.java | 90 +++-- .../org/apache/juneau/rest/RestMethodContext.java | 67 ++- .../juneau/rest/RestMethodContextBuilder.java | 58 ++- .../java/org/apache/juneau/rest/RestMethods.java | 17 +- .../org/apache/juneau/rest/RestMethodsBuilder.java | 54 ++- .../java/org/apache/juneau/rest/RestServlet.java | 4 +- .../org/apache/juneau/rest/annotation/Rest.java | 64 +-- .../juneau/rest/annotation/RestAnnotation.java | 78 +++- .../apache/juneau/rest/annotation/RestMethod.java | 4 +- .../rest/annotation/RestMethodAnnotation.java | 18 +- pom.xml | 2 +- 27 files changed, 1020 insertions(+), 258 deletions(-) diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/BeanFactory_Test.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/BeanFactory_Test.java index 1ddd4a7..17fd8c8 100644 --- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/BeanFactory_Test.java +++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/BeanFactory_Test.java @@ -39,7 +39,7 @@ public class BeanFactory_Test { bf.addBean(A.class, new A()); assertBoolean(bf.hasBean(A.class)).isTrue(); assertObject(bf.getBean(A.class)).exists(); - bf = new BeanFactory(bf, null); + bf = BeanFactory.of(bf); assertBoolean(bf.hasBean(A.class)).isTrue(); assertObject(bf.getBean(A.class)).exists(); } @@ -270,7 +270,7 @@ public class BeanFactory_Test { public void d01b_createBean_construct_withArgs_inner() throws Exception { BeanFactory bf = new BeanFactory(); assertThrown(()->bf.createBean(D1b.class)).stderr().is("Could not instantiate class "+CNAME+"$D1b: Public constructor found but could not find prerequisites: BeanFactory_Test,A."); - BeanFactory bf2 = new BeanFactory(null,this); + BeanFactory bf2 = BeanFactory.of(null,this); assertThrown(()->bf2.createBean(D1b.class)).stderr().is("Could not instantiate class "+CNAME+"$D1b: Public constructor found but could not find prerequisites: A."); bf2.addBean(A.class, new A()); assertObject(bf2.createBean(D1b.class)).exists(); diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanFactory.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanFactory.java index 8b148af..e0b2bd7 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanFactory.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanFactory.java @@ -61,6 +61,16 @@ public class BeanFactory { } /** + * Static creator. + * + * @param parent Parent bean factory. Can be <jk>null</jk> if this is the root resource. + * @return A new {@link BeanFactory} object. + */ + public static BeanFactory of(BeanFactory parent) { + return new BeanFactory(parent, null); + } + + /** * Default constructor. */ public BeanFactory() { diff --git a/juneau-examples/juneau-examples-rest-jetty/build-overlay/pom.xml b/juneau-examples/juneau-examples-rest-jetty/build-overlay/pom.xml index 19bc870..cdd1078 100644 --- a/juneau-examples/juneau-examples-rest-jetty/build-overlay/pom.xml +++ b/juneau-examples/juneau-examples-rest-jetty/build-overlay/pom.xml @@ -40,7 +40,7 @@ <maven.javadoc.skip>true</maven.javadoc.skip> <derby.version>${derby.version}</derby.version> - <springframework.version>${springframework.version}</springframework.version> + <springboot.version>${springboot.version}</springboot.version> <javax.inject.version>1</javax.inject.version> <juneau.version>${version}</juneau.version> <jena.version>${jena.version}</jena.version> @@ -64,7 +64,7 @@ <artifactId>juneau-marshall-rdf</artifactId> <version>\${juneau.version}</version> </dependency> - + <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> @@ -88,11 +88,11 @@ <version>\${hibernate.version}</version> </dependency> <dependency> - <groupId>xml-apis</groupId> - <artifactId>xml-apis</artifactId> - <version>\${xml.apis.version}</version> + <groupId>xml-apis</groupId> + <artifactId>xml-apis</artifactId> + <version>\${xml.apis.version}</version> </dependency> - + <!-- Optional RDF support --> <dependency> <groupId>org.apache.jena</groupId> @@ -131,11 +131,11 @@ <target>1.8</target> </configuration> </plugin> - + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> - <version>3.0.2</version> + <version>3.0.2</version> <executions> <execution> <id>package-config</id> @@ -200,5 +200,5 @@ </plugins> </build> - + </project> diff --git a/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml b/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml index cf76288..ee67ba8 100644 --- a/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml +++ b/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml @@ -37,7 +37,7 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>${springframework.version}</version> + <version>2.4.2</version> <relativePath /> </parent> @@ -52,6 +52,7 @@ <javax.inject.version>${javax.inject.version}</javax.inject.version> <juneau.version>${version}</juneau.version> <jena.version>${jena.version}</jena.version> + <springboot.version>2.4.2</springboot.version> </properties> <dependencies> @@ -140,6 +141,7 @@ <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> + <version>${springboot.version}</version> <executions> <execution> <goals> diff --git a/juneau-examples/juneau-examples-rest-springboot/pom.xml b/juneau-examples/juneau-examples-rest-springboot/pom.xml index b9b1b79..e0b414c 100644 --- a/juneau-examples/juneau-examples-rest-springboot/pom.xml +++ b/juneau-examples/juneau-examples-rest-springboot/pom.xml @@ -37,7 +37,6 @@ <!-- Java 8 required because Jetty requires it. --> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> - <springframework.version>2.0.1.RELEASE</springframework.version> </properties> @@ -59,7 +58,7 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> - <version>${springframework.version}</version> + <version>${springboot.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> @@ -87,6 +86,7 @@ <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> + <version>${springboot.version}</version> <executions> <execution> <goals> diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java index 1948382..775ef25 100644 --- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java +++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java @@ -294,7 +294,7 @@ public class MockRestClient extends RestClient implements HttpClientConnection { psb.set(MOCKRESTCLIENT_restBeanCtx, restBeanCtx); if (servletPath == null) - servletPath = toValidContextPath(restBeanCtx.getPath()); + servletPath = toValidContextPath(restBeanCtx.getFullPath()); rootUrl = rootUrl + emptyIfNull(contextPath) + emptyIfNull(servletPath); diff --git a/juneau-rest/juneau-rest-server-springboot/pom.xml b/juneau-rest/juneau-rest-server-springboot/pom.xml index f49dc04..a3da36f 100644 --- a/juneau-rest/juneau-rest-server-springboot/pom.xml +++ b/juneau-rest/juneau-rest-server-springboot/pom.xml @@ -39,7 +39,7 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> - <version>${springframework.version}</version> + <version>${springboot.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java index 67fb3ab..7b43f6b 100644 --- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java +++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java @@ -47,7 +47,7 @@ public class RestAnnotation_Test { .clientVersionHeader("clientVersionHeader") .config("config") .consumes("consumes") - .context(RestContext.class) + .contextClass(RestContext.class) .converters(RestConverter.class) .debug("debug") .debugOn("debugOn") @@ -63,6 +63,7 @@ public class RestAnnotation_Test { .guards(RestGuard.class) .infoProvider(BasicRestInfoProvider.class) .maxInput("maxInput") + .methodContextClass(RestMethodContext.class) .messages("messages") .on("on") .onClass(RestAnnotation_Test.class) @@ -73,6 +74,8 @@ public class RestAnnotation_Test { .produces("produces") .renderResponseStackTraces("renderResponseStackTraces") .responseHandlers(ResponseHandler.class) + .restChildrenClass(RestChildren.class) + .restMethodsClass(RestMethods.class) .restParams(RestParam.class) .roleGuard("roleGuard") .rolesDeclared("rolesDeclared") @@ -98,7 +101,7 @@ public class RestAnnotation_Test { .clientVersionHeader("clientVersionHeader") .config("config") .consumes("consumes") - .context(RestContext.class) + .contextClass(RestContext.class) .converters(RestConverter.class) .debug("debug") .debugOn("debugOn") @@ -114,6 +117,7 @@ public class RestAnnotation_Test { .guards(RestGuard.class) .infoProvider(BasicRestInfoProvider.class) .maxInput("maxInput") + .methodContextClass(RestMethodContext.class) .messages("messages") .on("on") .onClass(RestAnnotation_Test.class) @@ -124,6 +128,8 @@ public class RestAnnotation_Test { .produces("produces") .renderResponseStackTraces("renderResponseStackTraces") .responseHandlers(ResponseHandler.class) + .restChildrenClass(RestChildren.class) + .restMethodsClass(RestMethods.class) .restParams(RestParam.class) .roleGuard("roleGuard") .rolesDeclared("rolesDeclared") @@ -140,7 +146,7 @@ public class RestAnnotation_Test { @Test public void a01_basic() { - assertObject(a1).json().is("" + assertObject(a1).json().stderr().is("" + "{" + "allowedHeaderParams:'allowedHeaderParams'," + "allowedMethodHeaders:'allowedMethodHeaders'," @@ -151,7 +157,7 @@ public class RestAnnotation_Test { + "clientVersionHeader:'clientVersionHeader'," + "config:'config'," + "consumes:['consumes']," - + "context:'org.apache.juneau.rest.RestContext'," + + "contextClass:'org.apache.juneau.rest.RestContext'," + "converters:['org.apache.juneau.rest.RestConverter']," + "debug:'debug'," + "debugOn:'debugOn'," @@ -169,6 +175,7 @@ public class RestAnnotation_Test { + "infoProvider:'org.apache.juneau.rest.BasicRestInfoProvider'," + "maxInput:'maxInput'," + "messages:'messages'," + + "methodContextClass:'org.apache.juneau.rest.RestMethodContext'," + "on:['on']," + "onClass:['"+CNAME+"']," + "parsers:['org.apache.juneau.parser.Parser']," @@ -178,6 +185,8 @@ public class RestAnnotation_Test { + "produces:['produces']," + "renderResponseStackTraces:'renderResponseStackTraces'," + "responseHandlers:['org.apache.juneau.rest.ResponseHandler']," + + "restChildrenClass:'org.apache.juneau.rest.RestChildren'," + + "restMethodsClass:'org.apache.juneau.rest.RestMethods'," + "restParams:['org.apache.juneau.rest.RestParam']," + "roleGuard:'roleGuard'," + "rolesDeclared:'rolesDeclared'," @@ -248,7 +257,7 @@ public class RestAnnotation_Test { clientVersionHeader="clientVersionHeader", config="config", consumes="consumes", - context=RestContext.class, + contextClass=RestContext.class, converters=RestConverter.class, debug="debug", debugOn="debugOn", @@ -264,6 +273,7 @@ public class RestAnnotation_Test { guards=RestGuard.class, infoProvider=BasicRestInfoProvider.class, maxInput="maxInput", + methodContextClass=RestMethodContext.class, messages="messages", on="on", onClass=RestAnnotation_Test.class, @@ -274,6 +284,8 @@ public class RestAnnotation_Test { produces="produces", renderResponseStackTraces="renderResponseStackTraces", responseHandlers=ResponseHandler.class, + restChildrenClass=RestChildren.class, + restMethodsClass=RestMethods.class, restParams=RestParam.class, roleGuard="roleGuard", rolesDeclared="rolesDeclared", @@ -301,7 +313,7 @@ public class RestAnnotation_Test { clientVersionHeader="clientVersionHeader", config="config", consumes="consumes", - context=RestContext.class, + contextClass=RestContext.class, converters=RestConverter.class, debug="debug", debugOn="debugOn", @@ -317,6 +329,7 @@ public class RestAnnotation_Test { guards=RestGuard.class, infoProvider=BasicRestInfoProvider.class, maxInput="maxInput", + methodContextClass=RestMethodContext.class, messages="messages", on="on", onClass=RestAnnotation_Test.class, @@ -327,6 +340,8 @@ public class RestAnnotation_Test { produces="produces", renderResponseStackTraces="renderResponseStackTraces", responseHandlers=ResponseHandler.class, + restChildrenClass=RestChildren.class, + restMethodsClass=RestMethods.class, restParams=RestParam.class, roleGuard="roleGuard", rolesDeclared="rolesDeclared", diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodAnnotation_Test.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodAnnotation_Test.java index 3e46c26..e0b21c2 100644 --- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodAnnotation_Test.java +++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodAnnotation_Test.java @@ -36,7 +36,7 @@ public class RestMethodAnnotation_Test { RestMethod a1 = RestMethodAnnotation.create() .clientVersion("clientVersion") .consumes("consumes") - .context(RestMethodContext.class) + .contextClass(RestMethodContext.class) .converters(RestConverter.class) .debug("debug") .defaultAccept("defaultAccept") @@ -69,7 +69,7 @@ public class RestMethodAnnotation_Test { RestMethod a2 = RestMethodAnnotation.create() .clientVersion("clientVersion") .consumes("consumes") - .context(RestMethodContext.class) + .contextClass(RestMethodContext.class) .converters(RestConverter.class) .debug("debug") .defaultAccept("defaultAccept") @@ -105,7 +105,7 @@ public class RestMethodAnnotation_Test { + "{" + "clientVersion:'clientVersion'," + "consumes:['consumes']," - + "context:'org.apache.juneau.rest.RestMethodContext'," + + "contextClass:'org.apache.juneau.rest.RestMethodContext'," + "converters:['org.apache.juneau.rest.RestConverter']," + "debug:'debug'," + "defaultAccept:'defaultAccept'," @@ -181,7 +181,7 @@ public class RestMethodAnnotation_Test { @RestMethod( clientVersion="clientVersion", consumes="consumes", - context=RestMethodContext.class, + contextClass=RestMethodContext.class, converters=RestConverter.class, debug="debug", defaultAccept="defaultAccept", @@ -216,7 +216,7 @@ public class RestMethodAnnotation_Test { @RestMethod( clientVersion="clientVersion", consumes="consumes", - context=RestMethodContext.class, + contextClass=RestMethodContext.class, converters=RestConverter.class, debug="debug", defaultAccept="defaultAccept", diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Context_Test.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Context_Test.java index cba61b1..4280b10 100644 --- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Context_Test.java +++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Context_Test.java @@ -42,7 +42,7 @@ public class Rest_Context_Test { a1.get().run().assertBody().is("RestContext"); } - @Rest(context=X1.class) + @Rest(contextClass=X1.class) public static class A2 extends A1 {} @Test @@ -64,7 +64,7 @@ public class Rest_Context_Test { public static class A4 extends A1 { @RestHook(HookEvent.INIT) public void init(RestContextBuilder builder) throws Exception { - builder.context(X1.class); + builder.contextClass(X1.class); } } @@ -81,7 +81,7 @@ public class Rest_Context_Test { } } - @Rest(context=X2.class) + @Rest(contextClass=X2.class) public static class A5 { @RestMethod public String get(RestContext context) { diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Restx_Path_Test.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Restx_Path_Test.java index 46e1d75..abc51f0 100644 --- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Restx_Path_Test.java +++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/Restx_Path_Test.java @@ -30,20 +30,20 @@ public class Restx_Path_Test { public static class A { @RestMethod(path="/") public String a(RestContext c) { - return "A-" + c.getPath(); + return "A-" + c.getFullPath(); } } @Rest(path="/p1", children={A2.class}) public static class A1 { @RestMethod(path="/") public String a(RestContext c) { - return "A01-" + c.getPath(); + return "A01-" + c.getFullPath(); } } public static class A2a { @RestMethod(path="/") public String a(RestContext c) { - return "A02a-" + c.getPath(); + return "A02a-" + c.getFullPath(); } } @Rest(path="/p2") diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java index 2893ab9..2298946 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java @@ -60,7 +60,7 @@ public class RestCall { public RestCall(Object resource, RestContext context, HttpServletRequest req, HttpServletResponse res) { this.context = context; this.resource = resource; - beanFactory = new BeanFactory(context.rootBeanFactory, resource); + beanFactory = BeanFactory.of(context.rootBeanFactory, resource); beanFactory.addBean(RestContext.class, context); request(req).response(res); } diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildMatch.java similarity index 57% copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildMatch.java index 1c7f97f..23a3ecd 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildMatch.java @@ -12,50 +12,54 @@ // *************************************************************************************************************************** package org.apache.juneau.rest; -import java.util.*; - -import org.apache.juneau.collections.*; +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.util.*; /** - * Builder for {@link RestMethods} object. + * Represents a matched {@link Rest}-annotated child on an HTTP request. */ -public class RestMethodsBuilder { +public class RestChildMatch { + + private UrlPathMatch pathMatch; + private RestContext childContext; - TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>(); - Set<RestMethodContext> set = ASet.of(); + /** + * Creator. + * + * @param pathMatch The path matching results. + * @param childContext The child context. + * @return A new {@link RestChildMatch} object. + */ + public static RestChildMatch create(UrlPathMatch pathMatch, RestContext childContext) { + return new RestChildMatch(pathMatch, childContext); + } /** - * Adds a method context to this builder. + * Constructor. * - * @param mc The REST method context to add. - * @return Adds a method context to this builder. + * @param pathMatch The path matching results. + * @param childContext The child context. */ - public RestMethodsBuilder add(RestMethodContext mc) { - return add(mc.getHttpMethod(), mc); + protected RestChildMatch(UrlPathMatch pathMatch, RestContext childContext) { + this.pathMatch = pathMatch; + this.childContext = childContext; } /** - * Adds a method context to this builder. + * Returns the path matching results of the REST child match. * - * @param httpMethodName The HTTP method name. - * @param mc The REST method context to add. - * @return Adds a method context to this builder. + * @return The path matching results of the REST child match. */ - public RestMethodsBuilder add(String httpMethodName, RestMethodContext mc) { - httpMethodName = httpMethodName.toUpperCase(); - if (! map.containsKey(httpMethodName)) - map.put(httpMethodName, new TreeSet<>()); - map.get(httpMethodName).add(mc); - set.add(mc); - return this; + public UrlPathMatch getPathMatch() { + return pathMatch; } /** - * Creates a new {@link RestMethods} object using the contents of this builder. + * Returns the child context of the REST child match. * - * @return A new {@link RestMethods} object. + * @return The child context of the REST child match. */ - public RestMethods build() { - return new RestMethods(this); + public RestContext getChildContext() { + return childContext; } -} \ No newline at end of file +} diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildren.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildren.java new file mode 100644 index 0000000..ca054de --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildren.java @@ -0,0 +1,125 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest; + +import java.util.*; + +import javax.servlet.*; + +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.util.*; + +/** + * Implements the child resources of a {@link Rest}-annotated class. + */ +public class RestChildren { + + /** + * Represents a null value for the {@link Rest#restChildrenClass()} annotation. + */ + @SuppressWarnings("javadoc") + public final class Null extends RestChildren { + public Null(RestChildrenBuilder builder) throws Exception { + super(builder); + } + } + + private final Map<String,RestContext> children = Collections.synchronizedMap(new LinkedHashMap<String,RestContext>()); + + /** + * Creates a new builder for this object. + * + * @return A new builder for this object. + */ + public static RestChildrenBuilder create() { + return new RestChildrenBuilder(); + } + + /** + * Constructor. + * + * @param builder The builder containing the settings for this object. + */ + public RestChildren(RestChildrenBuilder builder) { + for (RestContext rc : builder.list) + children.put(rc.getPath(), rc); + } + + /** + * Looks through the registered children of this object and returns the best match. + * + * @param call The HTTP call. + * @return The child that best matches the call, or an empty {@link Optional} if a match could not be made. + */ + public Optional<RestChildMatch> findMatch(RestCall call) { + String pi = call.getPathInfoUndecoded(); + if ((! children.isEmpty()) && pi != null && ! pi.equals("/")) { + for (RestContext rc : children.values()) { + UrlPathMatcher upp = rc.pathMatcher; + UrlPathMatch uppm = upp.match(call.getUrlPath()); + if (uppm != null) { + return Optional.of(RestChildMatch.create(uppm, rc)); + } + } + } + return Optional.empty(); + } + + /** + * Returns the children in this object as a map. + * + * <p> + * The keys are the {@link RestContext#getPath() paths} of the child contexts. + * + * @return The children as an unmodifiable map. + */ + public Map<String,RestContext> asMap() { + return Collections.unmodifiableMap(children); + } + + + //----------------------------------------------------------------------------------------------------------------- + // Lifecycle methods. + //----------------------------------------------------------------------------------------------------------------- + + /** + * Called during servlet initialization on all children to invoke all {@link HookEvent#POST_INIT} methods. + * + * @throws ServletException Error occurred. + */ + public void postInit() throws ServletException { + for (RestContext childContext : children.values()) + childContext.postInit(); + } + + /** + * Called during servlet initialization on all children to invoke all {@link HookEvent#POST_INIT_CHILD_FIRST} methods. + * + * @throws ServletException Error occurred. + */ + public void postInitChildFirst() throws ServletException { + for (RestContext childContext : children.values()) + childContext.postInitChildFirst(); + } + + /** + * Called during servlet destruction on all children to invoke all {@link HookEvent#DESTROY} and {@link Servlet#destroy()} methods. + */ + public void destroy() { + for (RestContext r : children.values()) { + r.destroy(); + if (r.getResource() instanceof Servlet) + ((Servlet)r.getResource()).destroy(); + } + } +} diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildrenBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildrenBuilder.java new file mode 100644 index 0000000..25b01a8 --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestChildrenBuilder.java @@ -0,0 +1,92 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest; + +import static org.apache.juneau.rest.HttpRuntimeException.*; +import static org.apache.juneau.internal.ObjectUtils.*; + +import java.util.*; + +import org.apache.juneau.collections.*; +import org.apache.juneau.cp.*; +import org.apache.juneau.http.exception.*; + +/** + * Builder for {@link RestChildren} objects. + */ +public class RestChildrenBuilder { + + final List<RestContext> list = AList.create(); + + private BeanFactory beanFactory; + private Class<? extends RestChildren> implClass; + + /** + * Instantiates a {@link RestChildren} object based on the contents of this builder. + * + * @return A new {@link RestChildren} object. + */ + public RestChildren build() { + try { + Class<? extends RestChildren> ic = firstNonNull(implClass, RestChildren.class); + return BeanFactory.of(beanFactory).addBean(RestChildrenBuilder.class, this).createBean(ic); + } catch (Exception e) { + throw toHttpException(e, InternalServerError.class); + } + } + + /** + * Adds a child resource to this builder. + * + * @param childContext The REST context of the child resource. + * @return This object (for method chaining). + */ + public RestChildrenBuilder add(RestContext childContext) { + this.list.add(childContext); + return this; + } + + + /** + * Specifies a {@link RestChildren} implementation subclass to use. + * + * <p> + * When specified, the {@link #build()} method will create an instance of that class instead of the default {@link RestChildren}. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestChildrenBuilder} - This object. + * <li>Any beans found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * </ul> + * + * @param implClass The implementation class to build. + * @return This object (for method chaining). + */ + public RestChildrenBuilder implClass(Class<? extends RestChildren> implClass) { + this.implClass = implClass; + return this; + } + + /** + * Specifies a {@link BeanFactory} to use when resolving constructor arguments. + * + * @param beanFactory The bean factory to use for resolving constructor arguments. + * @return This object (for method chaining). + */ + public RestChildrenBuilder beanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + return this; + } +} diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java index 5c3212a..9c103d9 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java @@ -84,7 +84,9 @@ import org.apache.juneau.xml.*; @ConfigurableContext(nocache=true) public class RestContext extends BeanContext { - /** Represents a null value for the {@link Rest#context()} annotation.*/ + /** + * Represents a null value for the {@link Rest#contextClass()} annotation. + */ @SuppressWarnings("javadoc") public static final class Null extends RestContext { public Null(RestContextBuilder builder) throws Exception { @@ -1474,6 +1476,84 @@ public class RestContext extends BeanContext { public static final String REST_messages = PREFIX + ".messages.lo"; /** + * Configuration property: REST method context class. + * + * <h5 class='section'>Property:</h5> + * <ul class='spaced-list'> + * <li><b>ID:</b> {@link org.apache.juneau.rest.RestContext#REST_methodContextClass REST_methodContextClass} + * <li><b>Name:</b> <js>"RestContext.methodContextClass.c"</js> + * <li><b>Data type:</b> <c>Class<? extends {@link org.apache.juneau.rest.RestMethodContext}></c> + * <li><b>Default:</b> {@link org.apache.juneau.rest.RestMethodContext} + * <li><b>Session property:</b> <jk>false</jk> + * <li><b>Annotations:</b> + * <ul> + * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#methodContextClass()} + * <li class='ja'>{@link org.apache.juneau.rest.annotation.RestMethod#contextClass()} + * </ul> + * <li><b>Methods:</b> + * <ul> + * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#methodContextClass(Class)} + * <li class='jm'>{@link org.apache.juneau.rest.RestMethodContextBuilder#contextClass(Class)} + * </ul> + * </ul> + * + * <h5 class='section'>Description:</h5> + * <p> + * Allows you to extend the {@link RestMethodContext} class to modify how any of the methods are implemented. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestMethodContextBuilder} - The builder for the object. + * <li>Any beans found in the specified {@link #REST_beanFactory bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #REST_beanFactory bean factory}. + * </ul> + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * <jc>// Our extended context class that adds a request attribute to all requests.</jc> + * <jc>// The attribute value is provided by an injected spring bean.</jc> + * <jk>public</jk> MyRestMethodContext <jk>extends</jk> RestMethodContext { + * + * <jk>private final</jk> Optional<? <jk>extends</jk> Supplier<Object>> <jf>fooSupplier</jf>; + * + * <jc>// Constructor that takes in builder and optional injected attribute provider.</jc> + * <jk>public</jk> MyRestMethodContext(RestMethodContextBuilder <jv>builder</jv>, Optional<AnInjectedFooSupplier> <jv>fooSupplier</jv>) { + * <jk>super</jk>(<jv>builder</jv>); + * <jk>this</jk>.<jf>fooSupplier</jf> = <jv>fooSupplier</jv>.orElseGet(()-><jk>null</jk>); + * } + * + * <jc>// Override the method used to create default request attributes.</jc> + * <ja>@Override</ja> + * <jk>protected</jk> NamedAttributeList createDefaultRequestAttributes(Object <jv>resource</jv>, BeanFactory <jv>beanFactory</jv>, Method <jv>method</jv>, RestContext <jv>context</jv>) <jk>throws</jk> Exception { + * <jk>return super</jk> + * .createDefaultRequestAttributes(<jv>resource</jv>, <jv>beanFactory</jv>, <jv>method</jv>, <jv>context</jv>) + * .append(NamedAttribute.<jsm>of</jsm>(<js>"foo"</js>, ()-><jf>fooSupplier</jf>.get()); + * } + * } + * </p> + * <p class='bcode w800'> + * <jc>// Option #1 - Defined via annotation.</jc> + * <ja>@Rest</ja>(methodContextClass=MyRestMethodContext.<jk>class</jk>) + * <jk>public class</jk> MyResource { + * ... + * + * <jc>// Option #2 - Defined via builder passed in through init method.</jc> + * <ja>@RestHook</ja>(<jsf>INIT</jsf>) + * <jk>public void</jk> init(RestContextBuilder <jv>builder</jv>) <jk>throws</jk> Exception { + * <jv>builder</jv>.methodContextClass(MyRestMethodContext.<jk>class</jk>); + * } + * + * <ja>@RestMethod</ja> + * <jk>public</jk> Object getFoo(RequestAttributes <jv>attributes</jv>) { + * <jk>return</jk> <jv>attributes</jv>.get(<js>"foo"</js>); + * } + * } + * </p> + */ + public static final String REST_methodContextClass = PREFIX + ".methodContextClass.c"; + + /** * Configuration property: Parsers. * * <h5 class='section'>Property:</h5> @@ -2225,6 +2305,142 @@ public class RestContext extends BeanContext { public static final String REST_responseHandlers = PREFIX + ".responseHandlers.lo"; /** + * Configuration property: REST children class. + * + * <h5 class='section'>Property:</h5> + * <ul class='spaced-list'> + * <li><b>ID:</b> {@link org.apache.juneau.rest.RestContext#REST_restChildrenClass REST_restChildrenClass} + * <li><b>Name:</b> <js>"RestContext.restChildrenClass.c"</js> + * <li><b>Data type:</b> <c>Class<? extends {@link org.apache.juneau.rest.RestChildren}></c> + * <li><b>Default:</b> {@link org.apache.juneau.rest.RestChildren} + * <li><b>Session property:</b> <jk>false</jk> + * <li><b>Annotations:</b> + * <ul> + * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#restChildrenClass()} + * </ul> + * <li><b>Methods:</b> + * <ul> + * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#restChildrenClass(Class)} + * </ul> + * </ul> + * + * <h5 class='section'>Description:</h5> + * <p> + * Allows you to extend the {@link RestChildren} class to modify how any of the methods are implemented. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestChildrenBuilder} - The builder for the object. + * <li>Any beans found in the specified {@link #REST_beanFactory bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #REST_beanFactory bean factory}. + * </ul> + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * <jc>// Our extended context class</jc> + * <jk>public</jk> MyRestChildren <jk>extends</jk> RestChildren { + * <jk>public</jk> MyRestChildren(RestChildrenBuilder <jv>builder</jv>, ARequiredSpringBean <jv>bean1</jv>, Optional<AnOptionalSpringBean> <jv>bean2</jv>) { + * <jk>super</jk>(<jv>builder</jv>); + * } + * + * <jc>// Override any methods.</jc> + * + * <ja>@Override</ja> + * <jk>public</jk> Optional<RestChildMatch> findMatch(RestCall <jv>call</jv>) { + * String <jv>path</jv> = <jv>call</jv>.getPathInfo(); + * <jk>if</jk> (<jv>path</jv>.endsWith(<js>"/foo"</js>)) { + * <jc>// Do our own special handling.</jc> + * } + * <jk>return super</jk>.findMatch(<jv>call</jv>); + * } + * } + * </p> + * <p class='bcode w800'> + * <jc>// Option #1 - Defined via annotation.</jc> + * <ja>@Rest</ja>(restChildrenClass=MyRestChildren.<jk>class</jk>) + * <jk>public class</jk> MyResource { + * ... + * + * <jc>// Option #2 - Defined via builder passed in through init method.</jc> + * <ja>@RestHook</ja>(<jsf>INIT</jsf>) + * <jk>public void</jk> init(RestContextBuilder <jv>builder</jv>) <jk>throws</jk> Exception { + * <jv>builder</jv>.restChildrenClass(MyRestChildren.<jk>class</jk>); + * } + * } + * </p> + */ + public static final String REST_restChildrenClass = PREFIX + ".restChildrenClass.c"; + + /** + * Configuration property: REST methods class. + * + * <h5 class='section'>Property:</h5> + * <ul class='spaced-list'> + * <li><b>ID:</b> {@link org.apache.juneau.rest.RestContext#REST_restMethodsClass REST_restMethodsClass} + * <li><b>Name:</b> <js>"RestContext.restMethodsClass.c"</js> + * <li><b>Data type:</b> <c>Class<? extends {@link org.apache.juneau.rest.RestMethods}></c> + * <li><b>Default:</b> {@link org.apache.juneau.rest.RestMethods} + * <li><b>Session property:</b> <jk>false</jk> + * <li><b>Annotations:</b> + * <ul> + * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#restMethodsClass()} + * </ul> + * <li><b>Methods:</b> + * <ul> + * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#restMethodsClass(Class)} + * </ul> + * </ul> + * + * <h5 class='section'>Description:</h5> + * <p> + * Allows you to extend the {@link RestMethods} class to modify how any of the methods are implemented. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestMethodsBuilder} - The builder for the object. + * <li>Any beans found in the specified {@link #REST_beanFactory bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #REST_beanFactory bean factory}. + * </ul> + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * <jc>// Our extended context class</jc> + * <jk>public</jk> MyRestMethods <jk>extends</jk> RestMethods { + * <jk>public</jk> MyRestMethods(RestMethodsBuilder <jv>builder</jv>, ARequiredSpringBean <jv>bean1</jv>, Optional<AnOptionalSpringBean> <jv>bean2</jv>) { + * <jk>super</jk>(<jv>builder</jv>); + * } + * + * <jc>// Override any methods.</jc> + * + * <ja>@Override</ja> + * <jk>public</jk> RestMethodContext findMethod(RestCall <jv>call</jv>) <jk>throws</jk> MethodNotAllowed, PreconditionFailed, NotFound { + * String <jv>path</jv> = <jv>call</jv>.getPathInfo(); + * <jk>if</jk> (<jv>path</jv>.endsWith(<js>"/foo"</js>)) { + * <jc>// Do our own special handling.</jc> + * } + * <jk>return super</jk>.findMethod(<jv>call</jv>); + * } + * } + * </p> + * <p class='bcode w800'> + * <jc>// Option #1 - Defined via annotation.</jc> + * <ja>@Rest</ja>(restMethodsClass=MyRestMethods.<jk>class</jk>) + * <jk>public class</jk> MyResource { + * ... + * + * <jc>// Option #2 - Defined via builder passed in through init method.</jc> + * <ja>@RestHook</ja>(<jsf>INIT</jsf>) + * <jk>public void</jk> init(RestContextBuilder <jv>builder</jv>) <jk>throws</jk> Exception { + * <jv>builder</jv>.restMethodsClass(MyRestMethods.<jk>class</jk>); + * } + * } + * </p> + */ + public static final String REST_restMethodsClass = PREFIX + ".restMethodsClass.c"; + + /** * Configuration property: Java REST method parameter resolvers. * * <h5 class='section'>Property:</h5> @@ -2809,22 +3025,20 @@ public class RestContext extends BeanContext { /** * Configuration property: REST context class. * - * <review>NEEDS REVIEW</review> - * * <h5 class='section'>Property:</h5> * <ul class='spaced-list'> - * <li><b>ID:</b> {@link org.apache.juneau.rest.RestContext#REST_context REST_context} - * <li><b>Name:</b> <js>"RestContext.context.c"</js> + * <li><b>ID:</b> {@link org.apache.juneau.rest.RestContext#REST_contextClass REST_contextClass} + * <li><b>Name:</b> <js>"RestContext.contextClass.c"</js> * <li><b>Data type:</b> <c>Class<? extends {@link org.apache.juneau.rest.RestContext}></c> * <li><b>Default:</b> {@link org.apache.juneau.rest.RestContext} * <li><b>Session property:</b> <jk>false</jk> * <li><b>Annotations:</b> * <ul> - * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#context()} + * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#contextClass()} * </ul> * <li><b>Methods:</b> * <ul> - * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#context(Class)} + * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#contextClass(Class)} * </ul> * </ul> * @@ -2833,37 +3047,56 @@ public class RestContext extends BeanContext { * Allows you to extend the {@link RestContext} class to modify how any of the methods are implemented. * * <p> - * The subclass must provide the following: + * The subclass must have a public constructor that takes in any of the following arguments: * <ul> - * <li>A public constructor that takes in one parameter that should be passed to the super constructor: {@link RestContextBuilder}. + * <li>{@link RestContextBuilder} - The builder for the object. + * <li>Any beans found in the specified {@link #REST_beanFactory bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #REST_beanFactory bean factory}. * </ul> * * <h5 class='section'>Example:</h5> * <p class='bcode w800'> - * <jc>// Our extended context class</jc> + * <jc>// Our extended context class that adds a request attribute to all requests.</jc> + * <jc>// The attribute value is provided by an injected spring bean.</jc> * <jk>public</jk> MyRestContext <jk>extends</jk> RestContext { - * <jk>public</jk> MyRestContext(RestContextBuilder <jv>builder</jv>) { + * + * <jk>private final</jk> Optional<? <jk>extends</jk> Supplier<Object>> <jf>fooSupplier</jf>; + * + * <jc>// Constructor that takes in builder and optional injected attribute provider.</jc> + * <jk>public</jk> MyRestContext(RestMethodContextBuilder <jv>builder</jv>, Optional<AnInjectedFooSupplier> <jv>fooSupplier</jv>) { * <jk>super</jk>(<jv>builder</jv>); + * <jk>this</jk>.<jf>fooSupplier</jf> = <jv>fooSupplier</jv>.orElseGet(()-><jk>null</jk>); * } * - * <jc>// Override any methods.</jc> + * <jc>// Override the method used to create default request attributes.</jc> + * <ja>@Override</ja> + * <jk>protected</jk> NamedAttributeList createDefaultRequestAttributes(Object <jv>resource</jv>, BeanFactory <jv>beanFactory</jv>) <jk>throws</jk> Exception { + * <jk>return super</jk> + * .createDefaultRequestAttributes(<jv>resource</jv>, <jv>beanFactory</jv>) + * .append(NamedAttribute.<jsm>of</jsm>(<js>"foo"</js>, ()-><jf>fooSupplier</jf>.get()); + * } * } * </p> * <p class='bcode w800'> * <jc>// Option #1 - Defined via annotation.</jc> - * <ja>@Rest</ja>(context=MyRestContext.<jk>class</jk>) + * <ja>@Rest</ja>(contextClass=MyRestContext.<jk>class</jk>) * <jk>public class</jk> MyResource { * ... * * <jc>// Option #2 - Defined via builder passed in through init method.</jc> * <ja>@RestHook</ja>(<jsf>INIT</jsf>) * <jk>public void</jk> init(RestContextBuilder <jv>builder</jv>) <jk>throws</jk> Exception { - * <jv>builder</jv>.context(MyRestContext.<jk>class</jk>); + * <jv>builder</jv>.contextClass(MyRestContext.<jk>class</jk>); + * } + * + * <ja>@RestMethod</ja> + * <jk>public</jk> Object getFoo(RequestAttributes <jv>attributes</jv>) { + * <jk>return</jk> <jv>attributes</jv>.get(<js>"foo"</js>); * } * } * </p> */ - public static final String REST_context = PREFIX + ".context.c"; + public static final String REST_contextClass = PREFIX + ".context.c"; /** * Configuration property: Resource URI authority path. @@ -3162,7 +3395,7 @@ public class RestContext extends BeanContext { clientVersionHeader, uriAuthority, uriContext; - final String fullPath; + final String path, fullPath; final UrlPathMatcher pathMatcher; private final Set<String> allowedMethodParams, allowedHeaderParams, allowedMethodHeaders; @@ -3183,7 +3416,7 @@ public class RestContext extends BeanContext { private final Config config; private final VarResolver varResolver; private final RestMethods restMethods; - private final Map<String,RestContext> childResources; + private final RestChildren restChildren; private final StackTraceStore stackTraceStore; private final Logger logger; private final RestInfoProvider infoProvider; @@ -3346,14 +3579,13 @@ public class RestContext extends BeanContext { msgs = createMessages(r); fullPath = (builder.parentContext == null ? "" : (builder.parentContext.fullPath + '/')) + builder.getPath(); + path = builder.getPath(); - String p = builder.getPath(); + String p = path; if (! p.endsWith("/*")) p += "/*"; pathMatcher = UrlPathMatcher.of(p); - childResources = Collections.synchronizedMap(new LinkedHashMap<String,RestContext>()); // Not unmodifiable on purpose so that children can be replaced. - startCallMethods = createStartCallMethods(r).stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new); endCallMethods = createEndCallMethods(r).stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new); postInitMethods = createPostInitMethods(r).stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new); @@ -3364,42 +3596,7 @@ public class RestContext extends BeanContext { postCallMethods = createPostCallMethods(r).stream().map(this::toRestMethodInvoker).toArray(RestMethodInvoker[]:: new); restMethods = createRestMethods(r).build(); - - - // Initialize our child resources. - for (Object o : getArrayProperty(REST_children, Object.class)) { - String path = null; - - if (o instanceof RestChild) { - RestChild rc = (RestChild)o; - path = rc.path; - o = rc.resource; - } - - RestContextBuilder cb = null; - - if (o instanceof Class) { - Class<?> oc = (Class<?>)o; - // Don't allow specifying yourself as a child. Causes an infinite loop. - if (oc == builder.resourceClass) - continue; - cb = RestContext.create(this, builder.inner, oc, null); - o = new BeanFactory(beanFactory, r).addBean(RestContextBuilder.class, cb).createBean(oc); - } else { - cb = RestContext.create(this, builder.inner, o.getClass(), o); - } - - if (path != null) - cb.path(path); - - RestContext cc = cb.init(o).build(); - - MethodInfo mi = ClassInfo.of(o).getMethod("setContext", RestContext.class); - if (mi != null) - mi.accessible().invoke(o, cc); - - childResources.put(cb.getPath(), cc); - } + restChildren = createRestChildren(r).build(); infoProvider = createInfoProvider(r, beanFactory); @@ -3755,7 +3952,7 @@ public class RestContext extends BeanContext { x = (BeanFactory)resource; BeanFactory parent = parentContext == null ? null : parentContext.rootBeanFactory; - BeanFactory bf = new BeanFactory(parent, resource); + BeanFactory bf = BeanFactory.of(parent, resource); bf.addBean(BeanFactory.class, bf); if (x == null) @@ -4555,7 +4752,11 @@ public class RestContext extends BeanContext { * @throws Exception An error occurred. */ protected RestMethodsBuilder createRestMethods(Object resource) throws Exception { - RestMethodsBuilder x = new RestMethodsBuilder(); + RestMethodsBuilder x = RestMethods + .create() + .beanFactory(rootBeanFactory) + .implClass(getClassProperty(REST_restMethodsClass, RestMethods.class)); + ClassInfo rci = ClassInfo.of(resource); for (MethodInfo mi : rci.getPublicMethods()) { @@ -4575,8 +4776,12 @@ public class RestContext extends BeanContext { if (mi.isNotPublic()) throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", rci.inner().getName(), mi.getSimpleName()); - RestMethodContextBuilder rmcb = new RestMethodContextBuilder(mi.inner(), this); - RestMethodContext rmc = rmcb.build(); + RestMethodContext rmc = RestMethodContext + .create(mi.inner(), this) + .beanFactory(rootBeanFactory) + .implClass(getClassProperty(REST_methodContextClass, RestMethodContext.class)) + .build(); + String httpMethod = rmc.getHttpMethod(); // RRPC is a special case where a method returns an interface that we @@ -4584,9 +4789,11 @@ public class RestContext extends BeanContext { // We override the CallMethod.invoke() method to insert our logic. if ("RRPC".equals(httpMethod)) { - RestMethodContext smb = new RestMethodContextBuilder(mi.inner(), this) + RestMethodContext smb = RestMethodContext + .create(mi.inner(), this) .dotAll() - .context(RrpcRestMethodContext.class) + .beanFactory(rootBeanFactory) + .implClass(RrpcRestMethodContext.class) .build(); x .add("GET", smb) @@ -4605,6 +4812,56 @@ public class RestContext extends BeanContext { } /** + * Creates the builder for the children of this resource. + * + * @param resource The REST resource object. + * @return The builder for the {@link RestChildren} object. + * @throws Exception An error occurred. + */ + protected RestChildrenBuilder createRestChildren(Object resource) throws Exception { + RestChildrenBuilder x = RestChildren + .create() + .beanFactory(rootBeanFactory) + .implClass(getClassProperty(REST_restChildrenClass, RestChildren.class)); + + // Initialize our child resources. + for (Object o : getArrayProperty(REST_children, Object.class)) { + String path = null; + + if (o instanceof RestChild) { + RestChild rc = (RestChild)o; + path = rc.path; + o = rc.resource; + } + + RestContextBuilder cb = null; + + if (o instanceof Class) { + Class<?> oc = (Class<?>)o; + // Don't allow specifying yourself as a child. Causes an infinite loop. + if (oc == builder.resourceClass) + continue; + cb = RestContext.create(this, builder.inner, oc, null); + o = BeanFactory.of(beanFactory, resource).addBean(RestContextBuilder.class, cb).createBean(oc); + } else { + cb = RestContext.create(this, builder.inner, o.getClass(), o); + } + + if (path != null) + cb.path(path); + + RestContext cc = cb.init(o).build(); + + MethodInfo mi = ClassInfo.of(o).getMethod("setContext", RestContext.class); + if (mi != null) + mi.accessible().invoke(o, cc); + + x.add(cc); + } + return x; + } + + /** * Instantiates the list of {@link HookEvent#START_CALL} methods. * * @param resource The REST resource object. @@ -4835,7 +5092,7 @@ public class RestContext extends BeanContext { /** * Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or - * {@link RestContextBuilder#path(String)} method concatenated with those on all parent classes. + * {@link RestContextBuilder#path(String)} method. * * <p> * If path is not specified, returns <js>""</js>. @@ -4847,6 +5104,23 @@ public class RestContext extends BeanContext { * @return The servlet path. */ public String getPath() { + return path; + } + + /** + * Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or + * {@link RestContextBuilder#path(String)} method concatenated with those on all parent classes. + * + * <p> + * If path is not specified, returns <js>""</js>. + * + * <ul class='seealso'> + * <li class='jf'>{@link #REST_path} + * </ul> + * + * @return The full path. + */ + public String getFullPath() { return fullPath; } @@ -4938,7 +5212,7 @@ public class RestContext extends BeanContext { * Keys are the {@link Rest#path() @Rest(path)} annotation defined on the child resource. */ public Map<String,RestContext> getChildResources() { - return Collections.unmodifiableMap(childResources); + return restChildren.asMap(); } /** @@ -5351,7 +5625,7 @@ public class RestContext extends BeanContext { List<ClassInfo> pt = mi.getParamTypes(); RestParam[] rp = new RestParam[pt.size()]; - beanFactory = new BeanFactory(beanFactory, getResource()); + beanFactory = BeanFactory.of(beanFactory, getResource()); for (int i = 0; i < pt.size(); i++) { ParamInfo pi = mi.getParam(i); @@ -5384,7 +5658,7 @@ public class RestContext extends BeanContext { List<ClassInfo> pt = mi.getParamTypes(); RestParam[] rp = new RestParam[pt.size()]; - beanFactory = new BeanFactory(beanFactory, getResource()); + beanFactory = BeanFactory.of(beanFactory, getResource()); for (int i = 0; i < pt.size(); i++) { ParamInfo pi = mi.getParam(i); @@ -5499,24 +5773,20 @@ public class RestContext extends BeanContext { } // If this resource has child resources, try to recursively call them. - String pi = call.getPathInfoUndecoded(); - if ((! childResources.isEmpty()) && pi != null && ! pi.equals("/")) { - for (RestContext rc : getChildResources().values()) { - UrlPathMatcher upp = rc.pathMatcher; - UrlPathMatch uppm = upp.match(call.getUrlPath()); - if (uppm != null) { - if (! uppm.hasEmptyVars()) { - call.pathVars(uppm.getVars()); - HttpServletRequest childRequest = new OverrideableHttpServletRequest(call.getRequest()) - .pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix()))) - .servletPath(call.getServletPath() + uppm.getPrefix()); - rc.execute(rc.getResource(), childRequest, call.getResponse()); // TODO - resource needs to be dynamically retrieved. - } else { - call.debug(isDebug(call)).status(SC_NOT_FOUND).finish(); - } - return; - } + Optional<RestChildMatch> childMatch = restChildren.findMatch(call); + if (childMatch.isPresent()) { + UrlPathMatch uppm = childMatch.get().getPathMatch(); + RestContext rc = childMatch.get().getChildContext(); + if (! uppm.hasEmptyVars()) { + call.pathVars(uppm.getVars()); + HttpServletRequest childRequest = new OverrideableHttpServletRequest(call.getRequest()) + .pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix()))) + .servletPath(call.getServletPath() + uppm.getPrefix()); + rc.execute(rc.getResource(), childRequest, call.getResponse()); + } else { + call.debug(isDebug(call)).status(SC_NOT_FOUND).finish(); } + return; } if (isDebug(call)) @@ -5835,8 +6105,7 @@ public class RestContext extends BeanContext { throw new ServletException(e.unwrap()); } } - for (RestContext childContext : this.childResources.values()) - childContext.postInit(); + restChildren.postInit(); return this; } @@ -5849,8 +6118,7 @@ public class RestContext extends BeanContext { public RestContext postInitChildFirst() throws ServletException { if (initialized.get()) return this; - for (RestContext childContext : this.childResources.values()) - childContext.postInitChildFirst(); + restChildren.postInitChildFirst(); for (MethodInvoker x : postInitChildFirstMethods) { try { x.invokeUsingFactory(beanFactory, getResource()); @@ -5863,7 +6131,7 @@ public class RestContext extends BeanContext { } /** - * Called during servlet initialization to invoke all {@link HookEvent#DESTROY} methods. + * Called during servlet destruction to invoke all {@link HookEvent#DESTROY} methods. */ protected void destroy() { for (MethodInvoker x : destroyMethods) { @@ -5874,11 +6142,7 @@ public class RestContext extends BeanContext { } } - for (RestContext r : childResources.values()) { - r.destroy(); - if (r.getResource() instanceof Servlet) - ((Servlet)r.getResource()).destroy(); - } + restChildren.destroy(); } /** diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java index 5b60ff0..be2f879 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java @@ -184,8 +184,8 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon public RestContext build() { try { PropertyStore ps = getPropertyStore(); - Class<? extends RestContext> c = ps.getClassProperty(REST_context, RestContext.class, RestContext.class); - BeanFactory bf = new BeanFactory(beanFactory, resource); + Class<? extends RestContext> c = ps.getClassProperty(REST_contextClass, RestContext.class, RestContext.class); + BeanFactory bf = BeanFactory.of(beanFactory, resource); bf.addBean(RestContextBuilder.class, this); return bf.createBean(c); } catch (Exception e) { @@ -214,7 +214,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon if (x == null && parentContext.isPresent()) { x = parentContext.get().rootBeanFactory; } - return new BeanFactory(x, resource.orElse(null)); + return BeanFactory.of(x, resource.orElse(null)); } /** @@ -745,41 +745,19 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon /** * <i><l>RestContext</l> configuration property: </i> REST context class. * - * <review>NEEDS REVIEW</review> * <p> * Allows you to extend the {@link RestContext} class to modify how any of the methods are implemented. * - * <p> - * The subclass must provide the following: - * <ul> - * <li>A public constructor that takes in one parameter that should be passed to the super constructor: {@link RestContextBuilder}. + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_contextClass} * </ul> * - * <h5 class='section'>Example:</h5> - * <p class='bcode w800'> - * <jc>// Our REST class</jc> - * <ja>@Rest</ja>(context=MyRestContext.<jk>class</jk>) - * <jk>public class</jk> MyResource { - * ... - * } - * </p> - * <p class='bcode w800'> - * <ja>@Rest</ja> - * <jk>public class</jk> MyResource { - * ... - * <ja>@RestHook</ja>(<jsf>INIT</jsf>) - * <jk>public void</jk> init(RestContextBuilder <jv>builder</jv>) <jk>throws</jk> Exception { - * <jv>builder</jv>.context(MyRestContext.<jk>class</jk>); - * } - * } - * </p> - * * @param value The new value for this setting. * @return This object (for method chaining). */ @FluentSetter - public RestContextBuilder context(Class<? extends RestContext> value) { - return set(REST_context, value); + public RestContextBuilder contextClass(Class<? extends RestContext> value) { + return set(REST_contextClass, value); } /** @@ -1371,6 +1349,24 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon } /** + * <i><l>RestContext</l> configuration property: </i> REST method context class. + * + * <p> + * Allows you to extend the {@link RestMethodContext} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_methodContextClass} + * </ul> + * + * @param value The new value for this setting. + * @return This object (for method chaining). + */ + @FluentSetter + public RestContextBuilder methodContextClass(Class<? extends RestMethodContext> value) { + return set(REST_methodContextClass, value); + } + + /** * <i><l>RestContext</l> configuration property: </i> Parser listener. * * <p> @@ -1628,6 +1624,42 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon } /** + * <i><l>RestContext</l> configuration property: </i> REST children class. + * + * <p> + * Allows you to extend the {@link RestChildren} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_restChildrenClass} + * </ul> + * + * @param value The new value for this setting. + * @return This object (for method chaining). + */ + @FluentSetter + public RestContextBuilder restChildrenClass(Class<? extends RestChildren> value) { + return set(REST_restChildrenClass, value); + } + + /** + * <i><l>RestContext</l> configuration property: </i> REST methods class. + * + * <p> + * Allows you to extend the {@link RestMethods} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_restMethodsClass} + * </ul> + * + * @param value The new value for this setting. + * @return This object (for method chaining). + */ + @FluentSetter + public RestContextBuilder restMethodsClass(Class<? extends RestMethods> value) { + return set(REST_restMethodsClass, value); + } + + /** * <i><l>RestContext</l> configuration property: </i> Java method parameter resolvers. * * <p> diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java index 1f1d218..59ec1cc 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java @@ -65,7 +65,7 @@ import org.apache.juneau.utils.*; @ConfigurableContext(nocache=true) public class RestMethodContext extends BeanContext implements Comparable<RestMethodContext> { - /** Represents a null value for the {@link RestMethod#context()} annotation.*/ + /** Represents a null value for the {@link RestMethod#contextClass()} annotation.*/ @SuppressWarnings("javadoc") public static final class Null extends RestMethodContext { public Null(RestMethodContextBuilder builder) throws Exception { @@ -168,18 +168,20 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet * Configuration property: REST method context class. * * <ul class='spaced-list'> - * <li><b>ID:</b> {@link org.apache.juneau.rest.RestMethodContext#RESTMETHOD_context RESTMETHOD_context} - * <li><b>Name:</b> <js>"RestMethodContext.context.c"</js> + * <li><b>ID:</b> {@link org.apache.juneau.rest.RestMethodContext#RESTMETHOD_contextClass RESTMETHOD_contextClass} + * <li><b>Name:</b> <js>"RestMethodContext.contextClass.c"</js> * <li><b>Data type:</b> <c>Class<? extends {@link org.apache.juneau.rest.RestMethodContext}></c> * <li><b>Default:</b> {@link org.apache.juneau.rest.RestMethodContext} * <li><b>Session property:</b> <jk>false</jk> * <li><b>Annotations:</b> * <ul> - * <li class='ja'>{@link org.apache.juneau.rest.annotation.RestMethod#context()} + * <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#methodContextClass()} + * <li class='ja'>{@link org.apache.juneau.rest.annotation.RestMethod#contextClass()} * </ul> * <li><b>Methods:</b> * <ul> - * <li class='jm'>{@link org.apache.juneau.rest.RestMethodContextBuilder#context(Class)} + * <li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#methodContextClass(Class)} + * <li class='jm'>{@link org.apache.juneau.rest.RestMethodContextBuilder#contextClass(Class)} * </ul> * </ul> * @@ -188,12 +190,48 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet * Allows you to extend the {@link RestMethodContext} class to modify how any of the functions are implemented. * * <p> - * The subclass must provide the following: + * The subclass must have a public constructor that takes in any of the following arguments: * <ul> - * <li>A public constructor that takes in one parameter that should be passed to the super constructor: {@link RestMethodContextBuilder}. + * <li>{@link RestMethodContextBuilder} - The builder for the object. + * <li>Any beans found in the specified {@link #REST_beanFactory bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #REST_beanFactory bean factory}. * </ul> + * + * <h5 class='section'>Example:</h5> + * <p class='bcode w800'> + * <jc>// Our extended context class that adds a request attribute to all requests.</jc> + * <jc>// The attribute value is provided by an injected spring bean.</jc> + * <jk>public</jk> MyRestMethodContext <jk>extends</jk> RestMethodContext { + * + * <jk>private final</jk> Optional<? <jk>extends</jk> Supplier<Object>> <jf>fooSupplier</jf>; + * + * <jc>// Constructor that takes in builder and optional injected attribute provider.</jc> + * <jk>public</jk> MyRestMethodContext(RestMethodContextBuilder <jv>builder</jv>, Optional<AnInjectedFooSupplier> <jv>fooSupplier</jv>) { + * <jk>super</jk>(<jv>builder</jv>); + * <jk>this</jk>.<jf>fooSupplier</jf> = <jv>fooSupplier</jv>.orElseGet(()-><jk>null</jk>); + * } + * + * <jc>// Override the method used to create default request attributes.</jc> + * <ja>@Override</ja> + * <jk>protected</jk> NamedAttributeList createDefaultRequestAttributes(Object <jv>resource</jv>, BeanFactory <jv>beanFactory</jv>, Method <jv>method</jv>, RestContext <jv>context</jv>) <jk>throws</jk> Exception { + * <jk>return super</jk> + * .createDefaultRequestAttributes(<jv>resource</jv>, <jv>beanFactory</jv>, <jv>method</jv>, <jv>context</jv>) + * .append(NamedAttribute.<jsm>of</jsm>(<js>"foo"</js>, ()-><jf>fooSupplier</jf>.get()); + * } + * } + * </p> + * <p class='bcode w800'> + * <ja>@Rest</ja> + * <jk>public class</jk> MyResource { + * ... + * <ja>@RestMethod</ja>(contextClass=MyRestMethodContext.<jk>class</jk>) + * <jk>public</jk> Object getFoo(RequestAttributes <jv>attributes</jv>) { + * <jk>return</jk> <jv>attributes</jv>.get(<js>"foo"</js>); + * } + * } + * </p> */ - public static final String RESTMETHOD_context = PREFIX + ".context.c"; + public static final String RESTMETHOD_contextClass = PREFIX + ".contextClass.c"; /** * Configuration property: Debug mode. @@ -675,6 +713,17 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet final int hierarchyDepth; /** + * Creator. + * + * @param method The Java method this context belongs to. + * @param context The Java class context. + * @return A new builder. + */ + public static RestMethodContextBuilder create(java.lang.reflect.Method method, RestContext context) { + return new RestMethodContextBuilder(method, context); + } + + /** * Context constructor. * * @param builder The builder for this object. @@ -693,7 +742,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet mi = MethodInfo.of(method).accessible(); Object r = context.getResource(); - beanFactory = new BeanFactory(context.rootBeanFactory, r) + beanFactory = BeanFactory.of(context.rootBeanFactory, r) .addBean(RestMethodContext.class, this) .addBean(Method.class, method) .addBean(PropertyStore.class, ps); diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java index 02824cd..a542074 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java @@ -39,23 +39,29 @@ public class RestMethodContextBuilder extends BeanContextBuilder { RestContext restContext; Method restMethod; + private BeanFactory beanFactory; + private Class<? extends RestMethodContext> implClass; + @Override /* BeanContextBuilder */ public RestMethodContext build() { try { PropertyStore ps = getPropertyStore(); - Class<? extends RestMethodContext> c = ps.getClassProperty(RESTMETHOD_context, RestMethodContext.class, RestMethodContext.class); - BeanFactory bf = new BeanFactory(restContext.rootBeanFactory, restContext.getResource()); - bf.addBean(RestMethodContextBuilder.class, this); - return bf.createBean(c); + + Class<? extends RestMethodContext> ic = implClass; + if (ic == null) + ic = ps.getClassProperty(RESTMETHOD_contextClass, RestMethodContext.class, RestMethodContext.class); + + return BeanFactory.of(beanFactory).addBean(RestMethodContextBuilder.class, this).createBean(ic); } catch (Exception e) { throw toHttpException(e, InternalServerError.class); } } - RestMethodContextBuilder(java.lang.reflect.Method method, RestContext context) throws RestServletException { + RestMethodContextBuilder(java.lang.reflect.Method method, RestContext context) { this.restContext = context; this.restMethod = method; + this.beanFactory = context.rootBeanFactory; String sig = method.getDeclaringClass().getName() + '.' + method.getName(); MethodInfo mi = MethodInfo.of(context.getResource().getClass(), method); @@ -81,10 +87,8 @@ public class RestMethodContextBuilder extends BeanContextBuilder { applyAnnotations(mi.getAnnotationList(ConfigAnnotationFilter.INSTANCE), vrs); - } catch (RestServletException e) { - throw e; } catch (Exception e) { - throw new RestServletException(e, "Exception occurred while initializing method ''{0}''", sig); + throw toHttpException(e, InternalServerError.class); } } @@ -98,6 +102,40 @@ public class RestMethodContextBuilder extends BeanContextBuilder { return this; } + + /** + * Specifies a {@link RestMethodContext} implementation subclass to use. + * + * <p> + * When specified, the {@link #build()} method will create an instance of that class instead of the default {@link RestMethodContext}. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestMethodContextBuilder} - This object. + * <li>Any beans found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * </ul> + * + * @param implClass The implementation class to build. + * @return This object (for method chaining). + */ + public RestMethodContextBuilder implClass(Class<? extends RestMethodContext> implClass) { + this.implClass = implClass; + return this; + } + + /** + * Specifies a {@link BeanFactory} to use when resolving constructor arguments. + * + * @param beanFactory The bean factory to use for resolving constructor arguments. + * @return This object (for method chaining). + */ + public RestMethodContextBuilder beanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + return this; + } + //---------------------------------------------------------------------------------------------------- // Properties //---------------------------------------------------------------------------------------------------- @@ -135,8 +173,8 @@ public class RestMethodContextBuilder extends BeanContextBuilder { * @return This object (for method chaining). */ @FluentSetter - public RestMethodContextBuilder context(Class<? extends RestMethodContext> value) { - return set(RESTMETHOD_context, value); + public RestMethodContextBuilder contextClass(Class<? extends RestMethodContext> value) { + return set(RESTMETHOD_contextClass, value); } /** diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java index 2690e28..f9558ce 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java @@ -23,6 +23,16 @@ import org.apache.juneau.rest.annotation.*; */ public class RestMethods { + /** + * Represents a null value for the {@link Rest#restMethodsClass()} annotation. + */ + @SuppressWarnings("javadoc") + public final class Null extends RestMethods { + public Null(RestMethodsBuilder builder) throws Exception { + super(builder); + } + } + private final Map<String,List<RestMethodContext>> map; private List<RestMethodContext> list; @@ -35,7 +45,12 @@ public class RestMethods { return new RestMethodsBuilder(); } - RestMethods(RestMethodsBuilder builder) { + /** + * Constructor. + * + * @param builder The builder containing the settings for this object. + */ + public RestMethods(RestMethodsBuilder builder) { AMap<String,List<RestMethodContext>> m = AMap.create(); for (Map.Entry<String,TreeSet<RestMethodContext>> e : builder.map.entrySet()) m.put(e.getKey(), AList.of(e.getValue())); diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java index 1c7f97f..ef47f57 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java @@ -12,9 +12,14 @@ // *************************************************************************************************************************** package org.apache.juneau.rest; +import static org.apache.juneau.internal.ObjectUtils.*; +import static org.apache.juneau.rest.HttpRuntimeException.*; + import java.util.*; import org.apache.juneau.collections.*; +import org.apache.juneau.cp.*; +import org.apache.juneau.http.exception.*; /** * Builder for {@link RestMethods} object. @@ -24,6 +29,23 @@ public class RestMethodsBuilder { TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>(); Set<RestMethodContext> set = ASet.of(); + private BeanFactory beanFactory; + private Class<? extends RestMethods> implClass; + + /** + * Instantiates a {@link RestMethods} object based on the contents of this builder. + * + * @return A new {@link RestMethods} object. + */ + public RestMethods build() { + try { + Class<? extends RestMethods> ic = firstNonNull(implClass, RestMethods.class); + return BeanFactory.of(beanFactory).addBean(RestMethodsBuilder.class, this).createBean(ic); + } catch (Exception e) { + throw toHttpException(e, InternalServerError.class); + } + } + /** * Adds a method context to this builder. * @@ -51,11 +73,35 @@ public class RestMethodsBuilder { } /** - * Creates a new {@link RestMethods} object using the contents of this builder. + * Specifies a {@link RestMethods} implementation subclass to use. * - * @return A new {@link RestMethods} object. + * <p> + * When specified, the {@link #build()} method will create an instance of that class instead of the default {@link RestMethods}. + * + * <p> + * The subclass must have a public constructor that takes in any of the following arguments: + * <ul> + * <li>{@link RestMethodsBuilder} - This object. + * <li>Any beans found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * <li>Any {@link Optional} beans that may or may not be found in the specified {@link #beanFactory(BeanFactory) bean factory}. + * </ul> + * + * @param implClass The implementation class to build. + * @return This object (for method chaining). */ - public RestMethods build() { - return new RestMethods(this); + public RestMethodsBuilder implClass(Class<? extends RestMethods> implClass) { + this.implClass = implClass; + return this; + } + + /** + * Specifies a {@link BeanFactory} to use when resolving constructor arguments. + * + * @param beanFactory The bean factory to use for resolving constructor arguments. + * @return This object (for method chaining). + */ + public RestMethodsBuilder beanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + return this; } } \ No newline at end of file diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java index 05187e2..bff6623 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java @@ -74,7 +74,7 @@ public abstract class RestServlet extends HttpServlet { * @return A new bean factory. */ public BeanFactory createBeanFactory(Optional<BeanFactory> parent) { - return new BeanFactory(parent.orElse(null), this); + return BeanFactory.of(parent.orElse(null), this); } /** @@ -101,7 +101,7 @@ public abstract class RestServlet extends HttpServlet { public synchronized String getPath() { RestContext context = this.context.get(); if (context != null) - return context.getPath(); + return context.getFullPath(); ClassInfo ci = ClassInfo.of(getClass()); String path = ""; for (Rest rr : ci.getAnnotations(Rest.class)) diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java index b1eedd1..ec3470e 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java @@ -271,39 +271,16 @@ public @interface Rest { String[] consumes() default {}; /** - * Allows you to extend the {@link RestContext} class to modify how any of the methods are implemented. + * REST context class. * - * <review>NEEDS REVIEW</review> * <p> - * The subclass must provide the following: - * <ul> - * <li>A public constructor that takes in one parameter that should be passed to the super constructor: {@link RestContextBuilder}. - * </ul> - * - * <h5 class='section'>Example:</h5> - * <p class='bcode w800'> - * <jc>// Our extended context class</jc> - * <jk>public</jk> MyRestContext <jk>extends</jk> RestContext { - * <jk>public</jk> MyRestContext(RestContextBuilder <jv>builder</jv>) { - * <jk>super</jk>(<jv>builder</jv>); - * } - * - * // Override any methods. - * } - * </p> - * <p class='bcode w800'> - * <jc>// Our REST class</jc> - * <ja>@Rest</ja>(context=MyRestContext.<jk>class</jk>) - * <jk>public class</jk> MyResource { - * ... - * } - * </p> + * Allows you to extend the {@link RestContext} class to modify how any of the methods are implemented. * * <ul class='seealso'> - * <li class='jm'>{@link RestContextBuilder#context(Class)} + * <li class='jm'>{@link RestContext#REST_contextClass} * </ul> */ - Class<? extends RestContext> context() default RestContext.Null.class; + Class<? extends RestContext> contextClass() default RestContext.Null.class; /** * Class-level response converters. @@ -730,6 +707,15 @@ public @interface Rest { String messages() default ""; /** + * Allows you to extend the {@link RestMethodContext} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_methodContextClass} + * </ul> + */ + Class<? extends RestMethodContext> methodContextClass() default RestMethodContext.Null.class; + + /** * Dynamically apply this annotation to the specified classes. * * <ul class='seealso'> @@ -974,6 +960,30 @@ public @interface Rest { Class<? extends ResponseHandler>[] responseHandlers() default {}; /** + * REST children class. + * + * <p> + * Allows you to extend the {@link RestChildren} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_restChildrenClass} + * </ul> + */ + Class<? extends RestChildren> restChildrenClass() default RestChildren.Null.class; + + /** + * REST methods class. + * + * <p> + * Allows you to extend the {@link RestMethods} class to modify how any of the methods are implemented. + * + * <ul class='seealso'> + * <li class='jf'>{@link RestContext#REST_restMethodsClass} + * </ul> + */ + Class<? extends RestMethods> restMethodsClass() default RestMethods.Null.class; + + /** * Java method parameter resolvers. * * <p> diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java index 8e7f88b..f0cb789 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java @@ -90,12 +90,15 @@ public class RestAnnotation { Class<? extends StaticFiles> staticFiles = StaticFiles.Null.class; Class<? extends ResponseHandler>[] responseHandlers = new Class[0]; Class<? extends RestLogger> callLogger = RestLogger.Null.class; - Class<? extends RestContext> context = RestContext.Null.class; + Class<? extends RestContext> contextClass = RestContext.Null.class; Class<? extends RestConverter>[] converters = new Class[0]; Class<? extends RestGuard>[] guards = new Class[0]; Class<? extends RestInfoProvider> infoProvider=RestInfoProvider.Null.class; Class<? extends RestParam>[] restParams = new Class[0]; Class<? extends BeanFactory> beanFactory = BeanFactory.Null.class; + Class<? extends RestMethodContext> methodContextClass = RestMethodContext.Null.class; + Class<? extends RestChildren> restChildrenClass = RestChildren.Null.class; + Class<? extends RestMethods> restMethodsClass = RestMethods.Null.class; Class<?>[] children={}, parsers={}, serializers={}; ResourceSwagger swagger = ResourceSwaggerAnnotation.DEFAULT; String disableAllowBodyParam="", allowedHeaderParams="", allowedMethodHeaders="", allowedMethodParams="", clientVersionHeader="", config="", debug="", debugOn="", defaultAccept="", defaultCharset="", defaultContentType="", maxInput="", messages="", path="", renderResponseStackTraces="", roleGuard="", rolesDeclared="", siteName="", uriAuthority="", uriContext="", uriRelativity="", uriResolution=""; @@ -228,13 +231,13 @@ public class RestAnnotation { } /** - * Sets the {@link Rest#context()} property on this annotation. + * Sets the {@link Rest#contextClass()} property on this annotation. * * @param value The new value for this property. * @return This object (for method chaining). */ - public Builder context(Class<? extends RestContext> value) { - this.context = value; + public Builder contextClass(Class<? extends RestContext> value) { + this.contextClass = value; return this; } @@ -415,6 +418,17 @@ public class RestAnnotation { } /** + * Sets the {@link Rest#methodContextClass()} property on this annotation. + * + * @param value The new value for this property. + * @return This object (for method chaining). + */ + public Builder methodContextClass(Class<? extends RestMethodContext> value) { + this.methodContextClass = value; + return this; + } + + /** * Sets the {@link Rest#parsers()} property on this annotation. * * @param value The new value for this property. @@ -492,6 +506,28 @@ public class RestAnnotation { } /** + * Sets the {@link Rest#restChildrenClass()} property on this annotation. + * + * @param value The new value for this property. + * @return This object (for method chaining). + */ + public Builder restChildrenClass(Class<? extends RestChildren> value) { + this.restChildrenClass = value; + return this; + } + + /** + * Sets the {@link Rest#restMethodsClass()} property on this annotation. + * + * @param value The new value for this property. + * @return This object (for method chaining). + */ + public Builder restMethodsClass(Class<? extends RestMethods> value) { + this.restMethodsClass = value; + return this; + } + + /** * Sets the {@link Rest#restParams()} property on this annotation. * * @param value The new value for this property. @@ -655,12 +691,15 @@ public class RestAnnotation { private final Class<? extends StaticFiles> staticFiles; private final Class<? extends ResponseHandler>[] responseHandlers; private final Class<? extends RestLogger> callLogger; - private final Class<? extends RestContext> context; + private final Class<? extends RestContext> contextClass; private final Class<? extends RestConverter>[] converters; private final Class<? extends RestGuard>[] guards; private final Class<? extends RestInfoProvider> infoProvider; private final Class<? extends RestParam>[] restParams; private final Class<? extends BeanFactory> beanFactory; + private final Class<? extends RestMethodContext> methodContextClass; + private final Class<? extends RestChildren> restChildrenClass; + private final Class<? extends RestMethods> restMethodsClass; private final Class<?>[] children, parsers, serializers; private final ResourceSwagger swagger; private final String disableAllowBodyParam, allowedHeaderParams, allowedMethodHeaders, allowedMethodParams, clientVersionHeader, config, debug, debugOn, defaultAccept, defaultCharset, defaultContentType, maxInput, messages, path, renderResponseStackTraces, roleGuard, rolesDeclared, siteName, uriAuthority, uriContext, uriRelativity, uriResolution; @@ -678,7 +717,7 @@ public class RestAnnotation { this.clientVersionHeader = b.clientVersionHeader; this.config = b.config; this.consumes = copyOf(b.consumes); - this.context = b.context; + this.contextClass = b.contextClass; this.converters = copyOf(b.converters); this.debug = b.debug; this.debugOn = b.debugOn; @@ -695,6 +734,9 @@ public class RestAnnotation { this.infoProvider = b.infoProvider; this.maxInput = b.maxInput; this.messages = b.messages; + this.methodContextClass = b.methodContextClass; + this.restChildrenClass = b.restChildrenClass; + this.restMethodsClass = b.restMethodsClass; this.restParams = copyOf(b.restParams); this.parsers = copyOf(b.parsers); this.partParser = b.partParser; @@ -768,8 +810,8 @@ public class RestAnnotation { } @Override /* Rest */ - public Class<? extends RestContext> context() { - return context; + public Class<? extends RestContext> contextClass() { + return contextClass; } @Override /* Rest */ @@ -853,6 +895,11 @@ public class RestAnnotation { } @Override /* Rest */ + public Class<? extends RestMethodContext> methodContextClass() { + return methodContextClass; + } + + @Override /* Rest */ public Class<?>[] parsers() { return parsers; } @@ -888,6 +935,16 @@ public class RestAnnotation { } @Override /* Rest */ + public Class<? extends RestChildren> restChildrenClass() { + return restChildrenClass; + } + + @Override /* Rest */ + public Class<? extends RestMethods> restMethodsClass() { + return restMethodsClass; + } + + @Override /* Rest */ public Class<? extends RestParam>[] restParams() { return restParams; } @@ -985,7 +1042,7 @@ public class RestAnnotation { psb.prependTo(REST_guards, reverse(a.guards())); psb.prependTo(REST_children, a.children()); psb.prependTo(REST_restParams, a.restParams()); - psb.setIf(a.context() != RestContext.Null.class, REST_context, a.context()); + psb.setIf(a.contextClass() != RestContext.Null.class, REST_contextClass, a.contextClass()); psb.setIfNotEmpty(REST_uriContext, string(a.uriContext())); psb.setIfNotEmpty(REST_uriAuthority, string(a.uriAuthority())); psb.setIfNotEmpty(REST_uriRelativity, string(a.uriRelativity())); @@ -998,6 +1055,9 @@ public class RestAnnotation { psb.setIf(a.beanFactory() != BeanFactory.Null.class, REST_beanFactory, a.beanFactory()); psb.setIf(a.callLogger() != RestLogger.Null.class, REST_callLogger, a.callLogger()); psb.setIf(a.infoProvider() != RestInfoProvider.Null.class, REST_infoProvider, a.infoProvider()); + psb.setIf(a.methodContextClass() != RestMethodContext.Null.class, REST_methodContextClass, a.methodContextClass()); + psb.setIf(a.restChildrenClass() != RestChildren.Null.class, REST_restChildrenClass, a.restChildrenClass()); + psb.setIf(a.restMethodsClass() != RestMethods.Null.class, REST_restMethodsClass, a.restMethodsClass()); psb.setIfNotEmpty(REST_disableAllowBodyParam, bool(a.disableAllowBodyParam())); psb.setIfNotEmpty(REST_allowedHeaderParams, string(a.allowedHeaderParams())); psb.setIfNotEmpty(REST_allowedMethodHeaders, string(a.allowedMethodHeaders())); diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java index 7f0ed4a..7b9c113 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java @@ -126,10 +126,10 @@ public @interface RestMethod { * </ul> * * <ul class='seealso'> - * <li class='jm'>{@link RestMethodContextBuilder#context(Class)} + * <li class='jm'>{@link RestMethodContextBuilder#contextClass(Class)} * </ul> */ - Class<? extends RestMethodContext> context() default RestMethodContext.Null.class; + Class<? extends RestMethodContext> contextClass() default RestMethodContext.Null.class; /** * Class-level response converters. diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodAnnotation.java index db221bf..9db07dd 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodAnnotation.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodAnnotation.java @@ -61,7 +61,7 @@ public class RestMethodAnnotation { Class<? extends RestConverter>[] converters = new Class[0]; Class<? extends RestGuard>[] guards = new Class[0]; Class<? extends RestMatcher>[] matchers = new Class[0]; - Class<? extends RestMethodContext> context = RestMethodContext.Null.class; + Class<? extends RestMethodContext> contextClass = RestMethodContext.Null.class; Class<?>[] encoders=new Class<?>[0], parsers=new Class<?>[0], serializers=new Class<?>[0]; int priority = 0; MethodSwagger swagger = MethodSwaggerAnnotation.DEFAULT; @@ -107,13 +107,13 @@ public class RestMethodAnnotation { } /** - * Sets the {@link RestMethod#context()} property on this annotation. + * Sets the {@link RestMethod#contextClass()} property on this annotation. * * @param value The new value for this property. * @return This object (for method chaining). */ - public Builder context(Class<? extends RestMethodContext> value) { - this.context = value; + public Builder contextClass(Class<? extends RestMethodContext> value) { + this.contextClass = value; return this; } @@ -425,7 +425,7 @@ public class RestMethodAnnotation { private final Class<? extends RestConverter>[] converters; private final Class<? extends RestGuard>[] guards; private final Class<? extends RestMatcher>[] matchers; - private final Class<? extends RestMethodContext> context; + private final Class<? extends RestMethodContext> contextClass; private final Class<?>[] encoders, parsers, serializers; private final int priority; private final MethodSwagger swagger; @@ -436,7 +436,7 @@ public class RestMethodAnnotation { super(b); this.clientVersion = b.clientVersion; this.consumes = copyOf(b.consumes); - this.context = b.context; + this.contextClass = b.contextClass; this.converters = copyOf(b.converters); this.debug = b.debug; this.defaultAccept = b.defaultAccept; @@ -477,8 +477,8 @@ public class RestMethodAnnotation { } @Override /* RestMethod */ - public Class<? extends RestMethodContext> context() { - return context; + public Class<? extends RestMethodContext> contextClass() { + return contextClass; } @Override /* RestMethod */ @@ -634,7 +634,7 @@ public class RestMethodAnnotation { psb.set(REST_serializers, merge(ConverterUtils.toType(psb.peek(REST_serializers), Object[].class), a.serializers())); psb.set(REST_parsers, merge(ConverterUtils.toType(psb.peek(REST_parsers), Object[].class), a.parsers())); psb.set(REST_encoders, merge(ConverterUtils.toType(psb.peek(REST_encoders), Object[].class), a.encoders())); - psb.setIf(a.context() != RestMethodContext.Null.class, RESTMETHOD_context, a.context()); + psb.setIf(a.contextClass() != RestMethodContext.Null.class, RESTMETHOD_contextClass, a.contextClass()); psb.setIfNotEmpty(REST_produces, stringList(a.produces())); psb.setIfNotEmpty(REST_consumes, stringList(a.consumes())); stringStream(a.defaultRequestHeaders()).map(x -> BasicHeader.ofPair(x)).forEach(x -> psb.appendTo(RESTMETHOD_defaultRequestHeaders, x)); diff --git a/pom.xml b/pom.xml index 3813479..c8f3260 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ <derby.version>10.10.2.0</derby.version> <hibernate.version>5.0.9.Final</hibernate.version> <javax.inject.version>1</javax.inject.version> - <springframework.version>2.0.1.RELEASE</springframework.version> + <springboot.version>2.4.2</springboot.version> <xml.apis.version>1.4.01</xml.apis.version> </properties>