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 58db5a9  REST API refactoring.
58db5a9 is described below

commit 58db5a98ac7103643f6587337722870500db6368
Author: JamesBognar <james.bog...@salesforce.com>
AuthorDate: Tue Jan 26 09:59:38 2021 -0500

    REST API refactoring.
---
 .../java/org/apache/juneau/rest/RestContext.java   |  663 +++++++---
 .../org/apache/juneau/rest/SwaggerProvider.java    | 1320 ++++++++++++++++++++
 .../apache/juneau/rest/SwaggerProviderBuilder.java |  115 ++
 3 files changed, 1958 insertions(+), 140 deletions(-)

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 d70c08e..f3a5e06 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
@@ -18,8 +18,10 @@ import static org.apache.juneau.internal.ObjectUtils.*;
 import static org.apache.juneau.internal.IOUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.rest.HttpRuntimeException.*;
+import static org.apache.juneau.rest.logging.RestLoggingDetail.*;
 import static org.apache.juneau.Enablement.*;
 import static java.util.Collections.*;
+import static java.util.logging.Level.*;
 import static java.util.Arrays.*;
 
 import java.io.*;
@@ -63,6 +65,7 @@ import org.apache.juneau.rest.converters.*;
 import org.apache.juneau.rest.logging.*;
 import org.apache.juneau.rest.params.*;
 import org.apache.juneau.http.exception.*;
+import org.apache.juneau.http.header.*;
 import org.apache.juneau.rest.reshandlers.*;
 import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.vars.*;
@@ -2893,6 +2896,34 @@ public class RestContext extends BeanContext {
        public static final String REST_staticFilesDefault = PREFIX + 
".staticFilesDefault.o";
 
        /**
+        * Configuration property:  Swagger provider class.
+        *
+        * <h5 class='section'>Property:</h5>
+        * <ul class='spaced-list'>
+        *      <li><b>ID:</b>  {@link 
org.apache.juneau.rest.RestContext#REST_swaggerProviderClass 
REST_swaggerProviderClass}
+        *      <li><b>Name:</b>  <js>"RestContext.swaggerProviderClass.c"</js>
+        *      <li><b>Data type:</b>  {@link 
org.apache.juneau.rest.SwaggerProvider}
+        *      <li><b>Default:</b>  {@link 
org.apache.juneau.rest.SwaggerProvider}
+        *      <li><b>Session property:</b>  <jk>false</jk>
+        *      <li><b>Annotations:</b>
+        *              <ul>
+        *                      <li class='ja'>{@link 
org.apache.juneau.rest.annotation.Rest#infoProvider()}
+        *              </ul>
+        *      <li><b>Methods:</b>
+        *              <ul>
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.RestContextBuilder#infoProvider(Class)}
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.RestContextBuilder#infoProvider(RestInfoProvider)}
+        *              </ul>
+        *
+        * <h5 class='section'>Description:</h5>
+        * <p>
+        * The default static file finder.
+        * <p>
+        * This setting is inherited from the parent context.
+        */
+       public static final String REST_swaggerProviderClass = PREFIX + 
".swaggerProviderClass.c";
+
+       /**
         * Configuration property:  Supported accept media types.
         *
         * <h5 class='section'>Property:</h5>
@@ -3421,6 +3452,7 @@ public class RestContext extends BeanContext {
        private final StackTraceStore stackTraceStore;
        private final Logger logger;
        private final RestInfoProvider infoProvider;
+       private final SwaggerProvider swaggerProvider;
        private final HttpException initException;
        private final RestContext parentContext;
        final BeanFactory rootBeanFactory;
@@ -3503,9 +3535,9 @@ public class RestContext extends BeanContext {
                        parentContext = builder.parentContext;
                        ClassInfo rci = ClassInfo.ofProxy(r);
 
-                       rootBeanFactory = createRootBeanFactory(r);
+                       rootBeanFactory = createBeanFactory(r);
 
-                       beanFactory = createBeanFactory(r);
+                       beanFactory = BeanFactory.of(rootBeanFactory, r);
                        beanFactory.addBean(BeanFactory.class, beanFactory);
                        beanFactory.addBean(RestContext.class, this);
                        beanFactory.addBean(Object.class, r);
@@ -3597,10 +3629,11 @@ public class RestContext extends BeanContext {
                        preCallMethods = 
createPreCallMethods(r).stream().map(this::toRestMethodInvoker).toArray(RestMethodInvoker[]::
 new);
                        postCallMethods = 
createPostCallMethods(r).stream().map(this::toRestMethodInvoker).toArray(RestMethodInvoker[]::
 new);
 
-                       restMethods = createRestMethods(r).build();
-                       restChildren = createRestChildren(r).build();
+                       restMethods = createRestMethods(r);
+                       restChildren = createRestChildren(r);
 
                        infoProvider = createInfoProvider(r, beanFactory);
+                       swaggerProvider = createSwaggerProvider(r, beanFactory);
 
                } catch (HttpException e) {
                        _initException = e;
@@ -3622,9 +3655,85 @@ public class RestContext extends BeanContext {
        }
 
        /**
+        * Instantiates the bean factory for this REST resource.
+        *
+        * <p>
+        * The bean factory is typically used for passing in injected beans 
into REST contexts and for storing beans
+        * created by the REST context.
+        *
+        * <p>
+        * Instantiates based on the following logic:
+        * <ul>
+        *      <li>Returns the resource class itself if it's an instance of 
{@link BeanFactory}.
+        *      <li>Looks for {@link #REST_beanFactory} value set via any of 
the following:
+        *              <ul>
+        *                      <li>{@link 
RestContextBuilder#beanFactory(Class)}/{@link 
RestContextBuilder#beanFactory(BeanFactory)}
+        *                      <li>{@link Rest#beanFactory()}.
+        *              </ul>
+        *      <li>Instantiates a new {@link BeanFactory}.
+        *              Uses the parent context's root bean factory as the 
parent bean factory if this is a child resource.
+        * </ul>
+        *
+        * <p>
+        * Your REST class can also implement a create method called 
<c>createBeanFactory()</c> to instantiate your own
+        * bean factory.
+        *
+        * <h5 class='figure'>Example:</h5>
+        * <p class='bpcode w800'>
+        *      <ja>@Rest</ja>
+        *      <jk>public class</jk> MyRestClass {
+        *
+        *              <jk>public</jk> BeanFactory 
createBeanFactory(Optional&lt;BeanFactory&gt; <jv>parentBeanFactory</jv>) 
<jk>throws</jk> Exception {
+        *                      <jc>// Create your own bean factory here.</jc>
+        *              }
+        *      }
+        * </p>
+        *
+        * <p>
+        * The <c>createBeanFactory()</c> method can be static or non-static 
can contain any of the following arguments:
+        * <ul>
+        *      <li><c>{@link Optional}&lt;{@link BeanFactory}&gt;</c> - The 
parent root bean factory if this is a child resource.
+        * </ul>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_beanFactory}
+        * </ul>
+        *
+        * @param resource The REST resource object.
+        * @return The bean factory for this REST resource.
+        * @throws Exception If bean factory could not be instantiated.
+        */
+       protected BeanFactory createBeanFactory(Object resource) throws 
Exception {
+
+               BeanFactory x = null;
+
+               if (resource instanceof BeanFactory)
+                       x = (BeanFactory)resource;
+
+               if (x == null && parentContext != null)
+                       x = parentContext.rootBeanFactory;
+
+               if (x == null)
+                       x = getInstanceProperty(REST_beanFactory, 
BeanFactory.class, null, x);
+
+               x = BeanFactory
+                       .of(x, resource)
+                       .addBean(BeanFactory.class, x)
+                       .beanCreateMethodFinder(BeanFactory.class, resource)
+                       .find("createBeanFactory")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
         * Instantiates the file finder for this REST resource.
         *
         * <p>
+        * The file finder is used to retrieve localized files from the 
classpath.
+        *
+        * <p>
         * Instantiates based on the following logic:
         * <ul>
         *      <li>Returns the resource class itself is an instance of {@link 
FileFinder}.
@@ -3633,23 +3742,44 @@ public class RestContext extends BeanContext {
         *                      <li>{@link 
RestContextBuilder#fileFinder(Class)}/{@link 
RestContextBuilder#fileFinder(FileFinder)}
         *                      <li>{@link Rest#fileFinder()}.
         *              </ul>
-        *      <li>Looks for a static or non-static <c>createFileFinder()</> 
method that returns {@link FileFinder} on the
-        *              resource class with any of the following arguments:
-        *              <ul>
-        *                      <li>{@link RestContext}
-        *                      <li>{@link BeanFactory}
-        *                      <li>Any {@doc RestInjection injected beans}.
-        *              </ul>
-        *      <li>Resolves it via the bean factory registered in this context 
(including any Spring beans).
+        *      <li>Resolves it via the {@link #createBeanFactory(Object) bean 
factory} registered in this context (including Spring beans if using 
SpringRestServlet).
         *      <li>Looks for value in {@link #REST_fileFinderDefault} setting.
-        *      <li>Instantiates a {@link BasicFileFinder}.
+        *      <li>Instantiates via {@link #createFileFinderBuilder(Object, 
BeanFactory)}.
+        * </ul>
+        *
+        * <p>
+        * Your REST class can also implement a create method called 
<c>createFileFinder()</c> to instantiate your own
+        * file finder.
+        *
+        * <h5 class='figure'>Example:</h5>
+        * <p class='bpcode w800'>
+        *      <ja>@Rest</ja>
+        *      <jk>public class</jk> MyRestClass {
+        *
+        *              <jk>public</jk> FileFinder createFileFinder() 
<jk>throws</jk> Exception {
+        *                      <jc>// Create your own file finder here.</jc>
+        *              }
+        *      }
+        * </p>
+        *
+        * <p>
+        * The <c>createFileFinder()</c> method can be static or non-static can 
contain any of the following arguments:
+        * <ul>
+        *      <li>{@link FileFinder} - The file finder that would have been 
returned by this method.
+        *      <li>{@link FileFinderBuilder} - The file finder returned by 
{@link #createFileFinderBuilder(Object,BeanFactory)}.
+        *      <li>{@link RestContext} - This REST context.
+        *      <li>{@link BeanFactory} - The bean factory of this REST context.
+        *      <li>Any {@doc RestInjection injected bean} types.  Use {@link 
Optional} arguments for beans that may not exist.
+        * </ul>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_fileFinder}
         * </ul>
         *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The file finder for this REST resource.
         * @throws Exception If file finder could not be instantiated.
-        * @seealso #REST_fileFinder
         */
        protected FileFinder createFileFinder(Object resource, BeanFactory 
beanFactory) throws Exception {
 
@@ -3668,7 +3798,7 @@ public class RestContext extends BeanContext {
                        x = getInstanceProperty(REST_fileFinderDefault, 
FileFinder.class, null, beanFactory);
 
                if (x == null)
-                       x = new BasicFileFinder(this);
+                       x = createFileFinderBuilder(resource, 
beanFactory).build();
 
                x = BeanFactory
                        .of(beanFactory, resource)
@@ -3682,6 +3812,39 @@ public class RestContext extends BeanContext {
        }
 
        /**
+        * Instantiates the file finder builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createFileFinder(Object, BeanFactory)} method.
+        *
+        * @param resource The REST resource object.
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The file finder builder for this REST resource.
+        * @throws Exception If file finder builder could not be instantiated.
+        */
+       protected FileFinderBuilder createFileFinderBuilder(Object resource, 
BeanFactory beanFactory) throws Exception {
+
+               FileFinderBuilder x = FileFinder
+                       .create()
+                       .dir("static")
+                       .dir("htdocs")
+                       .cp(getResourceClass(), "htdocs", true)
+                       .cp(getResourceClass(), "/htdocs", true)
+                       .caching(1_000_000)
+                       .exclude("(?i).*\\.(class|properties)");
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(FileFinderBuilder.class, x)
+                       .beanCreateMethodFinder(FileFinderBuilder.class, 
resource)
+                       .find("createFileFinder")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
         * Instantiates the REST info provider for this REST resource.
         *
         * <p>
@@ -3704,11 +3867,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a {@link BasicRestInfoProvider}.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_infoProvider}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The info provider for this REST resource.
         * @throws Exception If info provider could not be instantiated.
-        * @seealso #REST_infoProvider
         */
        protected RestInfoProvider createInfoProvider(Object resource, 
BeanFactory beanFactory) throws Exception {
 
@@ -3762,11 +3928,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a {@link BasicStaticFiles}.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_staticFiles}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The file finder for this REST resource.
         * @throws Exception If file finder could not be instantiated.
-        * @seealso #REST_staticFiles
         */
        protected StaticFiles createStaticFiles(Object resource, BeanFactory 
beanFactory) throws Exception {
 
@@ -3785,7 +3954,7 @@ public class RestContext extends BeanContext {
                        x = getInstanceProperty(REST_staticFilesDefault, 
StaticFiles.class, null, beanFactory);
 
                if (x == null)
-                       x = new BasicStaticFiles(this);
+                       x = createStaticFilesBuilder(resource, 
beanFactory).build();
 
                x = BeanFactory
                        .of(beanFactory, resource)
@@ -3799,6 +3968,40 @@ public class RestContext extends BeanContext {
        }
 
        /**
+        * Instantiates the static files builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createStaticFiles(Object, BeanFactory)} method.
+        *
+        * @param resource The REST resource object.
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The static files builder for this REST resource.
+        * @throws Exception If static files builder could not be instantiated.
+        */
+       protected StaticFilesBuilder createStaticFilesBuilder(Object resource, 
BeanFactory beanFactory) throws Exception {
+
+               StaticFilesBuilder x = StaticFiles
+                       .create()
+                       .dir("static")
+                       .dir("htdocs")
+                       .cp(getResourceClass(), "htdocs", true)
+                       .cp(getResourceClass(), "/htdocs", true)
+                       .caching(1_000_000)
+                       .exclude("(?i).*\\.(class|properties)")
+                       .headers(CacheControl.of("max-age=86400, public"));
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(StaticFilesBuilder.class, x)
+                       .beanCreateMethodFinder(StaticFilesBuilder.class, 
resource)
+                       .find("createStaticFiles")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
         * Instantiates the call logger this REST resource.
         *
         * <p>
@@ -3823,11 +4026,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a {@link BasicFileFinder}.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_callLogger}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The file finder for this REST resource.
         * @throws Exception If file finder could not be instantiated.
-        * @seealso #REST_callLogger
         */
        protected RestLogger createCallLogger(Object resource, BeanFactory 
beanFactory) throws Exception {
 
@@ -3846,7 +4052,7 @@ public class RestContext extends BeanContext {
                        x = getInstanceProperty(REST_callLoggerDefault, 
RestLogger.class, null, beanFactory);
 
                if (x == null)
-                       x = new BasicRestLogger(this);
+                       x = createCallLoggerBuilder(resource, 
beanFactory).build();
 
                x = BeanFactory
                        .of(beanFactory, resource)
@@ -3860,112 +4066,49 @@ public class RestContext extends BeanContext {
        }
 
        /**
-        * Instantiates the bean factory for this REST resource.
+        * Instantiates the call logger builder for this REST resource.
         *
         * <p>
-        * Instantiates based on the following logic:
-        * <ul>
-        *      <li>Returns the resource class itself is an instance of {@link 
BeanFactory}.
-        *      <li>Looks for {@link #REST_beanFactory} value set via any of 
the following:
-        *              <ul>
-        *                      <li>{@link 
RestContextBuilder#beanFactory(Class)}/{@link 
RestContextBuilder#beanFactory(BeanFactory)}
-        *                      <li>{@link Rest#beanFactory()}.
-        *              </ul>
-        *      <li>Looks for a static or non-static <c>beanFactory()</> method 
that returns {@link BeanFactory} on the
-        *              resource class with any of the following arguments:
-        *              <ul>
-        *                      <li>{@link RestContext}
-        *                      <li>{@link BeanFactory} - The parent resource 
bean factory if this is a child.
-        *                      <li>Any {@doc RestInjection injected beans}.
-        *              </ul>
-        *      <li>Resolves it via the bean factory registered in this context.
-        *      <li>Instantiates a {@link BeanFactory}.
-        * </ul>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createCallLogger(Object, BeanFactory)} method.
         *
         * @param resource The REST resource object.
-        * @return The bean factory for this REST resource.
-        * @throws Exception If bean factory could not be instantiated.
-        * @seealso #REST_beanFactory
-        */
-       protected BeanFactory createBeanFactory(Object resource) throws 
Exception {
-
-               BeanFactory x = null;
-
-               if (resource instanceof BeanFactory)
-                       x = (BeanFactory)resource;
-
-               BeanFactory bf = createRootBeanFactory(resource)
-                       .addBean(RestContext.class, this)
-                       .addBean(BeanFactory.class, parentContext == null ? 
null : parentContext.rootBeanFactory)
-                       .addBean(PropertyStore.class, getPropertyStore())
-                       .addBean(Object.class, resource);
-
-               if (x == null)
-                       x = getInstanceProperty(REST_beanFactory, 
BeanFactory.class, null, bf);
-
-               if (x == null)
-                       x = bf;
-
-               x = bf
-                       .beanCreateMethodFinder(BeanFactory.class, resource)
-                       .find("createBeanFactory")
-                       .withDefault(x)
-                       .run();
-
-               return x;
-       }
-
-       /**
-        * Instantiates the root bean factory for this REST resource.
-        *
-        * <p>
-        * The root bean factory is the factory used for passing in injected 
beans.
-        * Beans created by this context are not added to this factory.
-        *
-        * <p>
-        * Instantiates based on the following logic:
-        * <ul>
-        *      <li>Returns the resource class itself is an instance of {@link 
BeanFactory}.
-        *      <li>Looks for {@link #REST_beanFactory} value set via any of 
the following:
-        *              <ul>
-        *                      <li>{@link 
RestContextBuilder#beanFactory(Class)}/{@link 
RestContextBuilder#beanFactory(BeanFactory)}
-        *                      <li>{@link Rest#beanFactory()}.
-        *              </ul>
-        *      <li>Looks for a static or non-static <c>beanFactory()</> method 
that returns {@link BeanFactory} on the
-        *              resource class with any of the following arguments:
-        *              <ul>
-        *                      <li>{@link RestContext}
-        *                      <li>{@link BeanFactory} - The parent resource 
bean factory if this is a child.
-        *              </ul>
-        *      <li>Resolves it via the bean factory registered in this context.
-        *      <li>Instantiates a {@link BeanFactory}.
-        * </ul>
-        *
-        * @param resource The REST resource object.
-        * @return The bean factory for this REST resource.
-        * @throws Exception If bean factory could not be instantiated.
-        * @seealso #REST_beanFactory
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The call logger builder for this REST resource.
+        * @throws Exception If call logger builder could not be instantiated.
         */
-       protected BeanFactory createRootBeanFactory(Object resource) throws 
Exception {
-
-               BeanFactory x = null;
-
-               if (resource instanceof BeanFactory)
-                       x = (BeanFactory)resource;
-
-               BeanFactory parent = parentContext == null ? null : 
parentContext.rootBeanFactory;
-               BeanFactory bf = BeanFactory.of(parent, resource);
-               bf.addBean(BeanFactory.class, bf);
-
-               if (x == null)
-                       x = getInstanceProperty(REST_beanFactory, 
BeanFactory.class, null, bf);
+       protected RestLoggerBuilder createCallLoggerBuilder(Object resource, 
BeanFactory beanFactory) throws Exception {
 
-               if (x == null)
-                       x = bf;
+               RestLoggerBuilder x = RestLogger
+                       .create()
+                       .normalRules(  // Rules when debugging is not enabled.
+                               RestLogger.createRule()  // Log 500+ errors 
with status-line and header information.
+                                       .statusFilter(a -> a >= 500)
+                                       .level(SEVERE)
+                                       .requestDetail(HEADER)
+                                       .responseDetail(HEADER)
+                                       .build(),
+                               RestLogger.createRule()  // Log 400-500 errors 
with just status-line information.
+                                       .statusFilter(a -> a >= 400)
+                                       .level(WARNING)
+                                       .requestDetail(STATUS_LINE)
+                                       .responseDetail(STATUS_LINE)
+                                       .build()
+                       )
+                       .debugRules(  // Rules when debugging is enabled.
+                               RestLogger.createRule()  // Log everything with 
full details.
+                                       .level(SEVERE)
+                                       .requestDetail(ENTITY)
+                                       .responseDetail(ENTITY)
+                                       .build()
+                       )
+                       .logger(getLogger())
+                       .stackTraceStore(getStackTraceStore());
 
-               x = bf
-                       .beanCreateMethodFinder(BeanFactory.class, resource)
-                       .find("createBeanFactory")
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(RestLoggerBuilder.class, x)
+                       .beanCreateMethodFinder(RestLoggerBuilder.class, 
resource)
+                       .find("createCallLogger")
                        .withDefault(x)
                        .run();
 
@@ -3994,11 +4137,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a <c>ResponseHandler[0]</c>.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_responseHandlers}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The response handlers for this REST resource.
         * @throws Exception If response handlers could not be instantiated.
-        * @seealso #REST_responseHandlers
         */
        protected ResponseHandlerList createResponseHandlers(Object resource, 
BeanFactory beanFactory) throws Exception {
 
@@ -4042,12 +4188,15 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a <c>Serializer[0]</c>.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_serializers}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @param ps The property store to apply to all serialiers.
         * @return The serializers for this REST resource.
         * @throws Exception If serializers could not be instantiated.
-        * @seealso #REST_serializers
         */
        protected SerializerGroup createSerializers(Object resource, 
BeanFactory beanFactory, PropertyStore ps) throws Exception {
 
@@ -4102,12 +4251,15 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates a <c>Parser[0]</c>.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_parsers}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @param ps The property store to apply to all serialiers.
         * @return The parsers for this REST resource.
         * @throws Exception If parsers could not be instantiated.
-        * @seealso #REST_parsers
         */
        protected ParserGroup createParsers(Object resource, BeanFactory 
beanFactory, PropertyStore ps) throws Exception {
 
@@ -4163,11 +4315,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates an {@link OpenApiSerializer}.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_partSerializer}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The HTTP part serializer for this REST resource.
         * @throws Exception If serializer could not be instantiated.
-        * @seealso #REST_partSerializer
         */
        protected HttpPartSerializer createPartSerializer(Object resource, 
BeanFactory beanFactory) throws Exception {
 
@@ -4219,11 +4374,14 @@ public class RestContext extends BeanContext {
         *      <li>Instantiates an {@link OpenApiSerializer}.
         * </ul>
         *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_partParser}
+        * </ul>
+        *
         * @param resource The REST resource object.
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The HTTP part parser for this REST resource.
         * @throws Exception If parser could not be instantiated.
-        * @seealso #REST_partParser
         */
        protected HttpPartParser createPartParser(Object resource, BeanFactory 
beanFactory) throws Exception {
 
@@ -4272,7 +4430,6 @@ public class RestContext extends BeanContext {
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The REST method parameter resolvers for this REST resource.
         * @throws Exception If parameter resolvers could not be instantiated.
-        * @seealso #REST_paramResolvers
         */
        @SuppressWarnings("unchecked")
        protected RestParamList createRestParams(Object resource, BeanFactory 
beanFactory) throws Exception {
@@ -4344,7 +4501,6 @@ public class RestContext extends BeanContext {
         * @param beanFactory The bean factory to use for retrieving and 
creating beans.
         * @return The REST method parameter resolvers for this REST resource.
         * @throws Exception If parameter resolvers could not be instantiated.
-        * @seealso #REST_paramResolvers
         */
        @SuppressWarnings("unchecked")
        protected RestParamList createHookMethodParams(Object resource, 
BeanFactory beanFactory) throws Exception {
@@ -4446,14 +4602,10 @@ public class RestContext extends BeanContext {
         * @throws Exception If JSON schema generator could not be instantiated.
         */
        protected JsonSchemaGenerator createJsonSchemaGenerator(Object 
resource, BeanFactory beanFactory) throws Exception {
-
                JsonSchemaGenerator x = 
beanFactory.getBean(JsonSchemaGenerator.class).orElse(null);
 
                if (x == null)
-                       x = JsonSchemaGenerator
-                               .create()
-                               .apply(getPropertyStore())
-                               .build();
+                       x = createJsonSchemaGeneratorBuilder(resource, 
beanFactory).build();
 
                x = BeanFactory
                        .of(beanFactory, resource)
@@ -4467,6 +4619,116 @@ public class RestContext extends BeanContext {
        }
 
        /**
+        * Instantiates the JSON-schema generator builder for this REST 
resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createJsonSchemaGenerator(Object, BeanFactory)} method.
+        *
+        * @param resource The REST resource object.
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The JSON-schema generator builder for this REST resource.
+        * @throws Exception If JSON-schema generator builder could not be 
instantiated.
+        */
+       protected JsonSchemaGeneratorBuilder 
createJsonSchemaGeneratorBuilder(Object resource, BeanFactory beanFactory) 
throws Exception {
+               JsonSchemaGeneratorBuilder x = JsonSchemaGenerator
+                       .create()
+                       .apply(getPropertyStore());
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(JsonSchemaGeneratorBuilder.class, x)
+                       
.beanCreateMethodFinder(JsonSchemaGeneratorBuilder.class, resource)
+                       .find("createJsonSchemaGenerator")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
+        * Instantiates the REST info provider for this REST resource.
+        *
+        * <p>
+        * Instantiates based on the following logic:
+        * <ul>
+        *      <li>Returns the resource class itself is an instance of {@link 
RestInfoProvider}.
+        *      <li>Looks for {@link #REST_infoProvider} value set via any of 
the following:
+        *              <ul>
+        *                      <li>{@link 
RestContextBuilder#infoProvider(Class)}/{@link 
RestContextBuilder#infoProvider(RestInfoProvider)}
+        *                      <li>{@link Rest#infoProvider()}.
+        *              </ul>
+        *      <li>Looks for a static or non-static <c>createInfoProvider()</> 
method that returns {@link RestInfoProvider} on the
+        *              resource class with any of the following arguments:
+        *              <ul>
+        *                      <li>{@link RestContext}
+        *                      <li>{@link BeanFactory}
+        *                      <li>Any {@doc RestInjection injected beans}.
+        *              </ul>
+        *      <li>Resolves it via the bean factory registered in this context.
+        *      <li>Instantiates a {@link BasicRestInfoProvider}.
+        * </ul>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link #REST_infoProvider}
+        * </ul>
+        *
+        * @param resource The REST resource object.
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The info provider for this REST resource.
+        * @throws Exception If info provider could not be instantiated.
+        */
+       protected SwaggerProvider createSwaggerProvider(Object resource, 
BeanFactory beanFactory) throws Exception {
+               SwaggerProvider x = 
beanFactory.getBean(SwaggerProvider.class).orElse(null);
+
+               if (x == null)
+                       x = createSwaggerProviderBuilder(resource, 
beanFactory).build();
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(SwaggerProvider.class, x)
+                       .beanCreateMethodFinder(SwaggerProvider.class, resource)
+                       .find("createSwaggerProvider")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
+        * Instantiates the REST API builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createSwaggerProvider(Object, BeanFactory)} method.
+        *
+        * @param resource The REST resource object.
+        * @param beanFactory The bean factory to use for retrieving and 
creating beans.
+        * @return The REST API builder for this REST resource.
+        * @throws Exception If REST API builder could not be instantiated.
+        */
+       protected SwaggerProviderBuilder createSwaggerProviderBuilder(Object 
resource, BeanFactory beanFactory) throws Exception {
+
+               SwaggerProviderBuilder x = SwaggerProvider
+                               .create()
+                               .beanFactory(beanFactory)
+                               .fileFinder(getFileFinder())
+                               .messages(getMessages())
+                               .varResolver(getVarResolver())
+                               
.jsonSchemaGenerator(createJsonSchemaGenerator(resource, beanFactory))
+                               
.implClass(getClassProperty(REST_swaggerProviderClass, SwaggerProvider.class, 
SwaggerProvider.class));
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(SwaggerProviderBuilder.class, x)
+                       .beanCreateMethodFinder(SwaggerProviderBuilder.class, 
resource)
+                       .find("createSwaggerProvider")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+
+       }
+
+       /**
         * Instantiates the variable resolver for this REST resource.
         *
         * <p>
@@ -4723,21 +4985,58 @@ public class RestContext extends BeanContext {
         * @throws Exception An error occurred.
         */
        protected Messages createMessages(Object resource) throws Exception {
+
+               Messages x = createMessagesBuilder(resource).build();
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(Messages.class, x)
+                       .beanCreateMethodFinder(Messages.class, resource)
+                       .find("createMessages")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
+        * Instantiates the Messages builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createMessages(Object)} method.
+        *
+        * @param resource The REST resource object.
+        * @return The messages builder for this REST resource.
+        * @throws Exception If messages builder could not be instantiated.
+        */
+       protected MessagesBuilder createMessagesBuilder(Object resource) throws 
Exception {
+
                Tuple2<Class<?>,String>[] mbl = 
getInstanceArrayProperty(REST_messages, Tuple2.class);
-               Messages msgs = null;
+               MessagesBuilder x = null;
+
                for (int i = mbl.length-1; i >= 0; i--) {
                        Class<?> c = firstNonNull(mbl[i].getA(), 
resource.getClass());
                        String value = mbl[i].getB();
                        if (isJsonObject(value,true)) {
-                               MessagesString x = 
SimpleJson.DEFAULT.read(value, MessagesString.class);
-                               msgs = 
Messages.create(c).name(x.name).baseNames(split(x.baseNames, 
',')).locale(x.locale).parent(msgs).build();
+                               MessagesString ms = 
SimpleJson.DEFAULT.read(value, MessagesString.class);
+                               x = 
Messages.create(c).name(ms.name).baseNames(split(ms.baseNames, 
',')).locale(ms.locale).parent(x == null ? null : x.build());
                        } else {
-                               msgs = 
Messages.create(c).name(value).parent(msgs).build();
+                               x = Messages.create(c).name(value).parent(x == 
null ? null : x.build());
                        }
                }
-               if (msgs == null)
-                       msgs = Messages.create(resource.getClass()).build();
-               return msgs;
+
+               if (x == null)
+                       x = Messages.create(resource.getClass());
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(MessagesBuilder.class, x)
+                       .beanCreateMethodFinder(MessagesBuilder.class, resource)
+                       .find("createMessages")
+                       .withDefault(x)
+                       .run();
+
+               return x;
        }
 
        private static class MessagesString {
@@ -4753,7 +5052,33 @@ public class RestContext extends BeanContext {
         * @return The builder for the {@link RestMethods} object.
         * @throws Exception An error occurred.
         */
-       protected RestMethodsBuilder createRestMethods(Object resource) throws 
Exception {
+       protected RestMethods createRestMethods(Object resource) throws 
Exception {
+
+               RestMethods x = createRestMethodsBuilder(resource).build();
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(RestMethods.class, x)
+                       .beanCreateMethodFinder(RestMethods.class, resource)
+                       .find("createRestMethods")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
+        * Instantiates the REST methods builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createRestMethods(Object)} method.
+        *
+        * @param resource The REST resource object.
+        * @return The REST methods builder for this REST resource.
+        * @throws Exception If REST methods builder could not be instantiated.
+        */
+       protected RestMethodsBuilder createRestMethodsBuilder(Object resource) 
throws Exception {
+
                RestMethodsBuilder x = RestMethods
                        .create()
                        .beanFactory(rootBeanFactory)
@@ -4810,6 +5135,14 @@ public class RestContext extends BeanContext {
                        }
                }
 
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(RestMethodsBuilder.class, x)
+                       .beanCreateMethodFinder(RestMethodsBuilder.class, 
resource)
+                       .find("createRestMethods")
+                       .withDefault(x)
+                       .run();
+
                return x;
        }
 
@@ -4820,7 +5153,33 @@ public class RestContext extends BeanContext {
         * @return The builder for the {@link RestChildren} object.
         * @throws Exception An error occurred.
         */
-       protected RestChildrenBuilder createRestChildren(Object resource) 
throws Exception {
+       protected RestChildren createRestChildren(Object resource) throws 
Exception {
+
+               RestChildren x = createRestChildrenBuilder(resource).build();
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(RestChildren.class, x)
+                       .beanCreateMethodFinder(RestChildren.class, resource)
+                       .find("createRestChildren")
+                       .withDefault(x)
+                       .run();
+
+               return x;
+       }
+
+       /**
+        * Instantiates the REST children builder for this REST resource.
+        *
+        * <p>
+        * Allows subclasses to intercept and modify the builder used by the 
{@link #createRestChildren(Object)} method.
+        *
+        * @param resource The REST resource object.
+        * @return The REST children builder for this REST resource.
+        * @throws Exception If REST children builder could not be instantiated.
+        */
+       protected RestChildrenBuilder createRestChildrenBuilder(Object 
resource) throws Exception {
+
                RestChildrenBuilder x = RestChildren
                        .create()
                        .beanFactory(rootBeanFactory)
@@ -4865,6 +5224,15 @@ public class RestContext extends BeanContext {
 
                        x.add(cc);
                }
+
+               x = BeanFactory
+                       .of(beanFactory, resource)
+                       .addBean(RestChildrenBuilder.class, x)
+                       .beanCreateMethodFinder(RestChildrenBuilder.class, 
resource)
+                       .find("createRestChildren")
+                       .withDefault(x)
+                       .run();
+
                return x;
        }
 
@@ -5177,6 +5545,21 @@ public class RestContext extends BeanContext {
        }
 
        /**
+        * Returns the Swagger provider used by this resource.
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_swaggerProviderClass}
+        * </ul>
+        *
+        * @return
+        *      The information provider for this resource.
+        *      <br>Never <jk>null</jk>.
+        */
+       public SwaggerProvider getSwaggerProvider() {
+               return swaggerProvider;
+       }
+
+       /**
         * Returns the resource object.
         *
         * <p>
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProvider.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProvider.java
new file mode 100644
index 0000000..ea01244
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProvider.java
@@ -0,0 +1,1320 @@
+// 
***************************************************************************************************************************
+// * 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.internal.ObjectUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.StringUtils.isEmpty;
+import static org.apache.juneau.rest.RestParamType.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.cp.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.annotation.Contact;
+import org.apache.juneau.http.annotation.License;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.jsonschema.*;
+import org.apache.juneau.jsonschema.annotation.*;
+import org.apache.juneau.jsonschema.annotation.Items;
+import org.apache.juneau.jsonschema.annotation.Tag;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.util.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Interface for retrieving Swagger on a REST resource.
+ */
+public class SwaggerProvider {
+
+       /**
+        * Creator.
+        *
+        * @return A new builder for this object.
+        */
+       public static SwaggerProviderBuilder create() {
+               return new SwaggerProviderBuilder();
+       }
+
+       private final VarResolverSession vr;
+       private final JsonParser jp = 
JsonParser.create().ignoreUnknownBeanProperties().build();
+       private final JsonSchemaGeneratorSession js;
+       private final Messages messages;
+       private final FileFinder fileFinder;
+
+       /**
+        * Constructor.
+        *
+        * @param builder The builder containing the settings for this Swagger 
provider.
+        */
+       public SwaggerProvider(SwaggerProviderBuilder builder) {
+               BeanFactory bf = builder.beanFactory;
+
+               this.vr = firstNonNull(builder.varResolver, 
bf.getBean(VarResolver.class).orElse(VarResolver.DEFAULT)).createSession();
+               this.js = firstNonNull(builder.jsonSchemaGenerator, 
bf.getBean(JsonSchemaGenerator.class).orElse(JsonSchemaGenerator.DEFAULT)).createSession();
+               this.messages = builder.messages;
+               this.fileFinder = builder.fileFinder;
+       }
+
+       /**
+        * Returns the Swagger associated with the specified {@link 
Rest}-annotated class.
+        *
+        * @param context The context of the {@link Rest}-annotated class.
+        * @param locale The request locale.
+        * @return A new {@link Swagger} object.
+        * @throws Exception If an error occurred producing the Swagger.
+        */
+       public Swagger getSwagger(RestContext context, Locale locale) throws 
Exception {
+
+               Class<?> c = context.getResourceClass();
+               ClassInfo rci = ClassInfo.of(c);
+
+               FileFinder ff = fileFinder != null ? fileFinder : 
FileFinder.create().cp(c,null,false).build();
+               Messages mb = messages != null ? messages : 
Messages.create(c).build();
+
+               InputStream is = ff.getStream(rci.getSimpleName() + ".json", 
locale).orElse(null);
+
+               // Load swagger JSON from classpath.
+               OMap omSwagger = SimpleJson.DEFAULT.read(is, OMap.class);
+               if (omSwagger == null)
+                       omSwagger = new OMap();
+
+               // Combine it with @Rest(swagger)
+               for (Rest rr : rci.getAnnotations(Rest.class)) {
+
+                       OMap sInfo = omSwagger.getMap("info", true);
+
+                       sInfo
+                               .appendSkipEmpty("title",
+                                       firstNonEmpty(
+                                               sInfo.getString("title"),
+                                               resolve(rr.title())
+                                       )
+                               )
+                               .appendSkipEmpty("description",
+                                       firstNonEmpty(
+                                               sInfo.getString("description"),
+                                               resolve(rr.description())
+                                       )
+                               );
+
+                       ResourceSwagger r = rr.swagger();
+
+                       omSwagger.append(parseMap(r.value(), 
"@ResourceSwagger(value) on class {0}", c));
+
+                       if (! ResourceSwaggerAnnotation.empty(r)) {
+                               OMap info = omSwagger.getMap("info", true);
+
+                               info
+                                       .appendSkipEmpty("title", 
resolve(r.title()))
+                                       .appendSkipEmpty("description", 
resolve(r.description()))
+                                       .appendSkipEmpty("version", 
resolve(r.version()))
+                                       .appendSkipEmpty("termsOfService", 
resolve(r.termsOfService()))
+                                       .appendSkipEmpty("contact",
+                                               merge(
+                                                       info.getMap("contact"),
+                                                       toMap(r.contact(), 
"@ResourceSwagger(contact) on class {0}", c)
+                                               )
+                                       )
+                                       .appendSkipEmpty("license",
+                                               merge(
+                                                       info.getMap("license"),
+                                                       toMap(r.license(), 
"@ResourceSwagger(license) on class {0}", c)
+                                               )
+                                       );
+                       }
+
+                       omSwagger
+                               .appendSkipEmpty("externalDocs",
+                                       merge(
+                                               
omSwagger.getMap("externalDocs"),
+                                               toMap(r.externalDocs(), 
"@ResourceSwagger(externalDocs) on class {0}", c)
+                                       )
+                               )
+                               .appendSkipEmpty("tags",
+                                       merge(
+                                               omSwagger.getList("tags"),
+                                               toList(r.tags(), 
"@ResourceSwagger(tags) on class {0}", c)
+                                       )
+                               );
+               }
+
+               omSwagger.appendSkipEmpty("externalDocs", 
parseMap(mb.findFirstString("externalDocs"), "Messages/externalDocs on class 
{0}", c));
+
+               OMap info = omSwagger.getMap("info", true);
+
+               info
+                       .appendSkipEmpty("title", 
resolve(mb.findFirstString("title")))
+                       .appendSkipEmpty("description", 
resolve(mb.findFirstString("description")))
+                       .appendSkipEmpty("version", 
resolve(mb.findFirstString("version")))
+                       .appendSkipEmpty("termsOfService", 
resolve(mb.findFirstString("termsOfService")))
+                       .appendSkipEmpty("contact", 
parseMap(mb.findFirstString("contact"), "Messages/contact on class {0}", c))
+                       .appendSkipEmpty("license", 
parseMap(mb.findFirstString("license"), "Messages/license on class {0}", c));
+
+               if (info.isEmpty())
+                       omSwagger.remove("info");
+
+               OList
+                       produces = omSwagger.getList("produces", true),
+                       consumes = omSwagger.getList("consumes", true);
+               if (consumes.isEmpty())
+                       consumes.addAll(context.getConsumes());
+               if (produces.isEmpty())
+                       produces.addAll(context.getProduces());
+
+               Map<String,OMap> tagMap = new LinkedHashMap<>();
+               if (omSwagger.containsKey("tags")) {
+                       for (OMap om : 
omSwagger.getList("tags").elements(OMap.class)) {
+                               String name = om.getString("name");
+                               if (name == null)
+                                       throw new SwaggerException(null, "Tag 
definition found without name in swagger JSON.");
+                               tagMap.put(name, om);
+                       }
+               }
+
+               String s = mb.findFirstString("tags");
+               if (s != null) {
+                       for (OMap m : parseListOrCdl(s, "Messages/tags on class 
{0}", c).elements(OMap.class)) {
+                               String name = m.getString("name");
+                               if (name == null)
+                                       throw new SwaggerException(null, "Tag 
definition found without name in resource bundle on class {0}", c) ;
+                               if (tagMap.containsKey(name))
+                                       tagMap.get(name).putAll(m);
+                               else
+                                       tagMap.put(name, m);
+                       }
+               }
+
+               // Load our existing bean definitions into our session.
+               OMap definitions = omSwagger.getMap("definitions", true);
+               for (String defId : definitions.keySet())
+                       js.addBeanDef(defId, new 
OMap(definitions.getMap(defId)));
+
+               // Iterate through all the @RestMethod methods.
+               for (RestMethodContext sm : context.getMethodContexts()) {
+
+                       BeanSession bs = sm.createBeanSession();
+
+                       Method m = sm.method;
+                       MethodInfo mi = MethodInfo.of(m);
+                       RestMethod rm = mi.getLastAnnotation(RestMethod.class);
+                       String mn = m.getName();
+
+                       // Get the operation from the existing swagger so far.
+                       OMap op = getOperation(omSwagger, sm.getPathPattern(), 
sm.getHttpMethod().toLowerCase());
+
+                       // Add @RestMethod(swagger)
+                       MethodSwagger ms = rm.swagger();
+
+                       op.append(parseMap(ms.value(), "@MethodSwagger(value) 
on class {0} method {1}", c, m));
+                       op.appendSkipEmpty("operationId",
+                               firstNonEmpty(
+                                       resolve(ms.operationId()),
+                                       op.getString("operationId"),
+                                       mn
+                               )
+                       );
+                       op.appendSkipEmpty("summary",
+                               firstNonEmpty(
+                                       resolve(ms.summary()),
+                                       resolve(mb.findFirstString(mn + 
".summary")),
+                                       op.getString("summary"),
+                                       resolve(rm.summary())
+                               )
+                       );
+                       op.appendSkipEmpty("description",
+                               firstNonEmpty(
+                                       resolve(ms.description()),
+                                       resolve(mb.findFirstString(mn + 
".description")),
+                                       op.getString("description"),
+                                       resolve(rm.description())
+                               )
+                       );
+                       op.appendSkipEmpty("deprecated",
+                               firstNonEmpty(
+                                       resolve(ms.deprecated()),
+                                       (m.getAnnotation(Deprecated.class) != 
null || m.getDeclaringClass().getAnnotation(Deprecated.class) != null) ? "true" 
: null
+                               )
+                       );
+                       op.appendSkipEmpty("tags",
+                               merge(
+                                       parseListOrCdl(mb.findFirstString(mn + 
".tags"), "Messages/tags on class {0} method {1}", c, m),
+                                       parseListOrCdl(ms.tags(), 
"@MethodSwagger(tags) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("schemes",
+                               merge(
+                                       parseListOrCdl(mb.findFirstString(mn + 
".schemes"), "Messages/schemes on class {0} method {1}", c, m),
+                                       parseListOrCdl(ms.schemes(), 
"@MethodSwagger(schemes) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("consumes",
+                               firstNonEmpty(
+                                       parseListOrCdl(mb.findFirstString(mn + 
".consumes"), "Messages/consumes on class {0} method {1}", c, m),
+                                       parseListOrCdl(ms.consumes(), 
"@MethodSwagger(consumes) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("produces",
+                               firstNonEmpty(
+                                       parseListOrCdl(mb.findFirstString(mn + 
".produces"), "Messages/produces on class {0} method {1}", c, m),
+                                       parseListOrCdl(ms.produces(), 
"@MethodSwagger(produces) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("parameters",
+                               merge(
+                                       parseList(mb.findFirstString(mn + 
".parameters"), "Messages/parameters on class {0} method {1}", c, m),
+                                       parseList(ms.parameters(), 
"@MethodSwagger(parameters) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("responses",
+                               merge(
+                                       parseMap(mb.findFirstString(mn + 
".responses"), "Messages/responses on class {0} method {1}", c, m),
+                                       parseMap(ms.responses(), 
"@MethodSwagger(responses) on class {0} method {1}", c, m)
+                               )
+                       );
+                       op.appendSkipEmpty("externalDocs",
+                               merge(
+                                       op.getMap("externalDocs"),
+                                       parseMap(mb.findFirstString(mn + 
".externalDocs"), "Messages/externalDocs on class {0} method {1}", c, m),
+                                       toMap(ms.externalDocs(), 
"@MethodSwagger(externalDocs) on class {0} method {1}", c, m)
+                               )
+                       );
+
+                       if (op.containsKey("tags"))
+                               for (String tag : 
op.getList("tags").elements(String.class))
+                                       if (! tagMap.containsKey(tag))
+                                               tagMap.put(tag, OMap.of("name", 
tag));
+
+                       OMap paramMap = new OMap();
+                       if (op.containsKey("parameters"))
+                               for (OMap param : 
op.getList("parameters").elements(OMap.class))
+                                       paramMap.put(param.getString("in") + 
'.' + ("body".equals(param.getString("in")) ? "body" : 
param.getString("name")), param);
+
+                       // Finally, look for parameters defined on method.
+                       for (ParamInfo mpi : mi.getParams()) {
+
+                               ClassInfo pt = mpi.getParameterType();
+                               Type type = pt.innerType();
+
+                               if (mpi.hasAnnotation(Body.class) || 
pt.hasAnnotation(Body.class)) {
+                                       OMap param = paramMap.getMap(BODY + 
".body", true).a("in", BODY);
+                                       for (Body a : 
mpi.getAnnotations(Body.class))
+                                               merge(param, a);
+                                       for (Body a : 
pt.getAnnotations(Body.class))
+                                               merge(param, a);
+                                       param.putIfAbsent("required", true);
+                                       param.appendSkipEmpty("schema", 
getSchema(param.getMap("schema"), type, bs));
+                                       addBodyExamples(sm, param, false, type, 
locale);
+
+                               } else if (mpi.hasAnnotation(Query.class) || 
pt.hasAnnotation(Query.class)) {
+                                       String name = null;
+                                       for (Query a : 
mpi.getAnnotations(Query.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       for (Query a : 
pt.getAnnotations(Query.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       OMap param = paramMap.getMap(QUERY + 
"." + name, true).a("name", name).a("in", QUERY);
+                                       for (Query a : 
mpi.getAnnotations(Query.class))
+                                               merge(param, a);
+                                       for (Query a : 
pt.getAnnotations(Query.class))
+                                               merge(param, a);
+                                       mergePartSchema(param, 
getSchema(param.getMap("schema"), type, bs));
+                                       addParamExample(sm, param, QUERY, type);
+
+                               } else if (mpi.hasAnnotation(FormData.class) || 
pt.hasAnnotation(FormData.class)) {
+                                       String name = null;
+                                       for (FormData a : 
mpi.getAnnotations(FormData.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       for (FormData a : 
pt.getAnnotations(FormData.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       OMap param = paramMap.getMap(FORM_DATA 
+ "." + name, true).a("name", name).a("in", FORM_DATA);
+                                       for (FormData a : 
mpi.getAnnotations(FormData.class))
+                                               merge(param, a);
+                                       for (FormData a : 
pt.getAnnotations(FormData.class))
+                                               merge(param, a);
+                                       mergePartSchema(param, 
getSchema(param.getMap("schema"), type, bs));
+                                       addParamExample(sm, param, FORM_DATA, 
type);
+
+                               } else if (mpi.hasAnnotation(Header.class) || 
pt.hasAnnotation(Header.class)) {
+                                       String name = null;
+                                       for (Header a : 
mpi.getAnnotations(Header.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       for (Header a : 
pt.getAnnotations(Header.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       OMap param = paramMap.getMap(HEADER + 
"." + name, true).a("name", name).a("in", HEADER);
+                                       for (Header a : 
mpi.getAnnotations(Header.class))
+                                               merge(param, a);
+                                       for (Header a : 
pt.getAnnotations(Header.class))
+                                               merge(param, a);
+                                       mergePartSchema(param, 
getSchema(param.getMap("schema"), type, bs));
+                                       addParamExample(sm, param, HEADER, 
type);
+
+                               } else if (mpi.hasAnnotation(Path.class) || 
pt.hasAnnotation(Path.class)) {
+                                       String name = null;
+                                       for (Path a : 
mpi.getAnnotations(Path.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       for (Path a : 
pt.getAnnotations(Path.class))
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       OMap param = paramMap.getMap(PATH + "." 
+ name, true).a("name", name).a("in", PATH);
+                                       for (Path a : 
mpi.getAnnotations(Path.class))
+                                               merge(param, a);
+                                       for (Path a : 
pt.getAnnotations(Path.class))
+                                               merge(param, a);
+                                       mergePartSchema(param, 
getSchema(param.getMap("schema"), type, bs));
+                                       addParamExample(sm, param, PATH, type);
+                                       param.putIfAbsent("required", true);
+                               }
+                       }
+
+                       if (! paramMap.isEmpty())
+                               op.put("parameters", paramMap.values());
+
+                       OMap responses = op.getMap("responses", true);
+
+                       for (ClassInfo eci : mi.getExceptionTypes()) {
+                               if (eci.hasAnnotation(Response.class)) {
+                                       List<Response> la = 
eci.getAnnotations(Response.class);
+                                       Set<Integer> codes = getCodes(la, 500);
+                                       for (Response a : la) {
+                                               for (Integer code : codes) {
+                                                       OMap om = 
responses.getMap(String.valueOf(code), true);
+                                                       merge(om, a);
+                                                       if (! 
om.containsKey("schema"))
+                                                               
om.appendSkipEmpty("schema", getSchema(om.getMap("schema"), eci.inner(), bs));
+                                               }
+                                       }
+                                       for (MethodInfo ecmi : 
eci.getAllMethodsParentFirst()) {
+                                               ResponseHeader a = 
ecmi.getLastAnnotation(ResponseHeader.class);
+                                               if (a == null)
+                                                       a = 
ecmi.getReturnType().unwrap(Value.class,Optional.class).getLastAnnotation(ResponseHeader.class);
+                                               if (a != null && ! isMulti(a)) {
+                                                       String ha = a.name();
+                                                       for (Integer code : 
codes) {
+                                                               OMap header = 
responses.getMap(String.valueOf(code), true).getMap("headers", true).getMap(ha, 
true);
+                                                               merge(header, 
a);
+                                                               
mergePartSchema(header, getSchema(header, ecmi.getReturnType().innerType(), 
bs));
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       if (mi.hasAnnotation(Response.class) || 
mi.getReturnType().unwrap(Value.class,Optional.class).hasAnnotation(Response.class))
 {
+                               List<Response> la = 
mi.getAnnotations(Response.class);
+                               Set<Integer> codes = getCodes(la, 200);
+                               for (Response a : la) {
+                                       for (Integer code : codes) {
+                                               OMap om = 
responses.getMap(String.valueOf(code), true);
+                                               merge(om, a);
+                                               if (! om.containsKey("schema"))
+                                                       
om.appendSkipEmpty("schema", getSchema(om.getMap("schema"), 
m.getGenericReturnType(), bs));
+                                               addBodyExamples(sm, om, true, 
m.getGenericReturnType(), locale);
+                                       }
+                               }
+                               if 
(mi.getReturnType().hasAnnotation(Response.class)) {
+                                       for (MethodInfo ecmi : 
mi.getReturnType().getAllMethodsParentFirst()) {
+                                               if 
(ecmi.hasAnnotation(ResponseHeader.class)) {
+                                                       ResponseHeader a = 
ecmi.getLastAnnotation(ResponseHeader.class);
+                                                       String ha = a.name();
+                                                       if (! isMulti(a)) {
+                                                               for (Integer 
code : codes) {
+                                                                       OMap 
header = responses.getMap(String.valueOf(code), true).getMap("headers", 
true).getMap(ha, true);
+                                                                       
merge(header, a);
+                                                                       
mergePartSchema(header, getSchema(header, ecmi.getReturnType().innerType(), 
bs));
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       } else if (m.getGenericReturnType() != void.class) {
+                               OMap om = responses.getMap("200", true);
+                               if (! om.containsKey("schema"))
+                                       om.appendSkipEmpty("schema", 
getSchema(om.getMap("schema"), m.getGenericReturnType(), bs));
+                               addBodyExamples(sm, om, true, 
m.getGenericReturnType(), locale);
+                       }
+
+                       // Finally, look for @ResponseHeader parameters defined 
on method.
+                       for (ParamInfo mpi : mi.getParams()) {
+
+                               ClassInfo pt = mpi.getParameterType();
+
+                               if (mpi.hasAnnotation(ResponseHeader.class) || 
pt.hasAnnotation(ResponseHeader.class)) {
+                                       List<ResponseHeader> la = 
AList.of(mpi.getAnnotations(ResponseHeader.class)).a(pt.getAnnotations(ResponseHeader.class));
+                                       Set<Integer> codes = getCodes2(la, 200);
+                                       String name = null;
+                                       for (ResponseHeader a : la)
+                                               name = firstNonEmpty(a.name(), 
a.n(), a.value(), name);
+                                       Type type = 
mpi.getParameterType().innerType();
+                                       for (ResponseHeader a : la) {
+                                               if (! isMulti(a)) {
+                                                       for (Integer code : 
codes) {
+                                                               OMap header = 
responses.getMap(String.valueOf(code), true).getMap("headers", 
true).getMap(name, true);
+                                                               merge(header, 
a);
+                                                               
mergePartSchema(header, getSchema(header, Value.getParameterType(type), bs));
+                                                       }
+                                               }
+                                       }
+
+                               } else if (mpi.hasAnnotation(Response.class) || 
pt.hasAnnotation(Response.class)) {
+                                       List<Response> la = 
AList.of(mpi.getAnnotations(Response.class)).a(pt.getAnnotations(Response.class));
+                                       Set<Integer> codes = getCodes(la, 200);
+                                       Type type = 
mpi.getParameterType().innerType();
+                                       for (Response a : la) {
+                                               for (Integer code : codes) {
+                                                       OMap response = 
responses.getMap(String.valueOf(code), true);
+                                                       merge(response, a);
+                                               }
+                                       }
+                                       type = Value.getParameterType(type);
+                                       if (type != null) {
+                                               for (String code : 
responses.keySet()) {
+                                                       OMap om = 
responses.getMap(code);
+                                                       if (! 
om.containsKey("schema"))
+                                                               
om.appendSkipEmpty("schema", getSchema(om.getMap("schema"), type, bs));
+                                               }
+                                       }
+                               }
+                       }
+
+                       // Add default response descriptions.
+                       for (Map.Entry<String,Object> e : responses.entrySet()) 
{
+                               String key = e.getKey();
+                               OMap val = responses.getMap(key);
+                               if (StringUtils.isDecimal(key))
+                                       val.appendIf(false, true, true, 
"description", RestUtils.getHttpResponseText(Integer.parseInt(key)));
+                       }
+
+                       if (responses.isEmpty())
+                               op.remove("responses");
+                       else
+                               op.put("responses", new TreeMap<>(responses));
+
+                       if (! op.containsKey("consumes")) {
+                               List<MediaType> mConsumes = 
sm.supportedContentTypes;
+                               if (! mConsumes.equals(consumes))
+                                       op.put("consumes", mConsumes);
+                       }
+
+                       if (! op.containsKey("produces")) {
+                               List<MediaType> mProduces = 
sm.supportedAcceptTypes;
+                               if (! mProduces.equals(produces))
+                                       op.put("produces", mProduces);
+                       }
+               }
+
+               if (js.getBeanDefs() != null)
+                       for (Map.Entry<String,OMap> e : 
js.getBeanDefs().entrySet())
+                               definitions.put(e.getKey(), 
fixSwaggerExtensions(e.getValue()));
+               if (definitions.isEmpty())
+                       omSwagger.remove("definitions");
+
+               if (! tagMap.isEmpty())
+                       omSwagger.put("tags", tagMap.values());
+
+               if (consumes.isEmpty())
+                       omSwagger.remove("consumes");
+               if (produces.isEmpty())
+                       omSwagger.remove("produces");
+
+//             try {
+//                     if (! omSwagger.isEmpty())
+//                             assertNoEmpties(omSwagger);
+//             } catch (SwaggerException e1) {
+//                     
System.err.println(omSwagger.toString(SimpleJsonSerializer.DEFAULT_READABLE));
+//                     throw e1;
+//             }
+
+               try {
+                       String swaggerJson = 
SimpleJsonSerializer.DEFAULT_READABLE.toString(omSwagger);
+//                     System.err.println(swaggerJson);
+                       return jp.parse(swaggerJson, Swagger.class);
+               } catch (Exception e) {
+                       throw new RestServletException(e, "Error detected in 
swagger.");
+               }
+       }
+       
//=================================================================================================================
+       // Utility methods
+       
//=================================================================================================================
+
+       private boolean isMulti(ResponseHeader h) {
+               if ("*".equals(h.name()) || "*".equals(h.value()))
+                       return true;
+               return false;
+       }
+
+       private OMap resolve(OMap om) throws ParseException {
+               OMap om2 = null;
+               if (om.containsKey("_value")) {
+                       om = om.modifiable();
+                       om2 = parseMap(om.remove("_value"));
+               } else {
+                       om2 = new OMap();
+               }
+               for (Map.Entry<String,Object> e : om.entrySet()) {
+                       Object val = e.getValue();
+                       if (val instanceof OMap) {
+                               val = resolve((OMap)val);
+                       } else if (val instanceof OList) {
+                               val = resolve((OList) val);
+                       } else if (val instanceof String) {
+                               val = resolve(val.toString());
+                       }
+                       om2.put(e.getKey(), val);
+               }
+               return om2;
+       }
+
+       private OList resolve(OList om) throws ParseException {
+               OList ol2 = new OList();
+               for (Object val : om) {
+                       if (val instanceof OMap) {
+                               val = resolve((OMap)val);
+                       } else if (val instanceof OList) {
+                               val = resolve((OList) val);
+                       } else if (val instanceof String) {
+                               val = resolve(val.toString());
+                       }
+                       ol2.add(val);
+               }
+               return ol2;
+       }
+
+       private String resolve(String[]...s) {
+               for (String[] ss : s) {
+                       if (ss.length != 0)
+                               return resolve(joinnl(ss));
+               }
+               return null;
+       }
+
+       private String resolve(String s) {
+               if (s == null)
+                       return null;
+               return vr.resolve(s.trim());
+       }
+
+       private OMap parseMap(String[] o, String location, Object...args) 
throws ParseException {
+               if (o.length == 0)
+                       return OMap.EMPTY_MAP;
+               try {
+                       return parseMap(o);
+               } catch (ParseException e) {
+                       throw new SwaggerException(e, "Malformed swagger JSON 
object encountered in " + location + ".", args);
+               }
+       }
+
+       private OMap parseMap(String o, String location, Object...args) throws 
ParseException {
+               try {
+                       return parseMap(o);
+               } catch (ParseException e) {
+                       throw new SwaggerException(e, "Malformed swagger JSON 
object encountered in " + location + ".", args);
+               }
+       }
+
+       private OMap parseMap(Object o) throws ParseException {
+               if (o == null)
+                       return null;
+               if (o instanceof String[])
+                       o = joinnl((String[])o);
+               if (o instanceof String) {
+                       String s = o.toString();
+                       if (s.isEmpty())
+                               return null;
+                       s = resolve(s);
+                       if ("IGNORE".equalsIgnoreCase(s))
+                               return OMap.of("ignore", true);
+                       if (! isJsonObject(s, true))
+                               s = "{" + s + "}";
+                       return OMap.ofJson(s);
+               }
+               if (o instanceof OMap)
+                       return (OMap)o;
+               throw new SwaggerException(null, "Unexpected data type ''{0}''. 
 Expected OMap or String.", o.getClass().getName());
+       }
+
+       private OList parseList(Object o, String location, 
Object...locationArgs) throws ParseException {
+               try {
+                       if (o == null)
+                               return null;
+                       String s = (o instanceof String[] ? joinnl((String[])o) 
: o.toString());
+                       if (s.isEmpty())
+                               return null;
+                       s = resolve(s);
+                       if (! isJsonArray(s, true))
+                               s = "[" + s + "]";
+                       return OList.ofJson(s);
+               } catch (ParseException e) {
+                       throw new SwaggerException(e, "Malformed swagger JSON 
array encountered in "+location+".", locationArgs);
+               }
+       }
+
+       private OList parseListOrCdl(Object o, String location, 
Object...locationArgs) throws ParseException {
+               try {
+                       if (o == null)
+                               return null;
+                       String s = (o instanceof String[] ? joinnl((String[])o) 
: o.toString());
+                       if (s.isEmpty())
+                               return null;
+                       s = resolve(s);
+                       return StringUtils.parseListOrCdl(s);
+               } catch (ParseException e) {
+                       throw new SwaggerException(e, "Malformed swagger JSON 
array encountered in "+location+".", locationArgs);
+               }
+       }
+
+       private OMap newMap(OMap om, String[] value, String location, 
Object...locationArgs) throws ParseException {
+               if (value.length == 0)
+                       return om == null ? new OMap() : om;
+                       OMap om2 = parseMap(joinnl(value), location, 
locationArgs);
+               if (om == null)
+                       return om2;
+               return om.append(om2);
+       }
+
+       private OMap merge(OMap...maps) {
+               OMap m = maps[0];
+               for (int i = 1; i < maps.length; i++) {
+                       if (maps[i] != null) {
+                               if (m == null)
+                                       m = new OMap();
+                               m.putAll(maps[i]);
+                       }
+               }
+               return m;
+       }
+
+       private OList merge(OList...lists) {
+               OList l = lists[0];
+               for (int i = 1; i < lists.length; i++) {
+                       if (lists[i] != null) {
+                               if (l == null)
+                                       l = new OList();
+                               l.addAll(lists[i]);
+                       }
+               }
+               return l;
+       }
+
+       @SafeVarargs
+       private final <T> T firstNonEmpty(T...t) {
+               return ObjectUtils.firstNonEmpty(t);
+       }
+
+       private OMap toMap(ExternalDocs a, String location, 
Object...locationArgs) throws ParseException {
+               if (ExternalDocsAnnotation.empty(a))
+                       return null;
+               OMap om = newMap(new OMap(), a.value(), location, locationArgs)
+                       .appendSkipEmpty("description", 
resolve(joinnl(a.description())))
+                       .appendSkipEmpty("url", resolve(a.url()));
+               return nullIfEmpty(om);
+       }
+
+       private OMap toMap(Contact a, String location, Object...locationArgs) 
throws ParseException {
+               if (ContactAnnotation.empty(a))
+                       return null;
+               OMap om = newMap(new OMap(), a.value(), location, locationArgs)
+                       .appendSkipEmpty("name", resolve(a.name()))
+                       .appendSkipEmpty("url", resolve(a.url()))
+                       .appendSkipEmpty("email", resolve(a.email()));
+               return nullIfEmpty(om);
+       }
+
+       private OMap toMap(License a, String location, Object...locationArgs) 
throws ParseException {
+               if (LicenseAnnotation.empty(a))
+                       return null;
+               OMap om = newMap(new OMap(), a.value(), location, locationArgs)
+                       .appendSkipEmpty("name", resolve(a.name()))
+                       .appendSkipEmpty("url", resolve(a.url()));
+               return nullIfEmpty(om);
+       }
+
+       private OMap toMap(Tag a, String location, Object...locationArgs) 
throws ParseException {
+               OMap om = newMap(new OMap(), a.value(), location, locationArgs);
+               om
+                       .appendSkipEmpty("name", resolve(a.name()))
+                       .appendSkipEmpty("description", 
resolve(joinnl(a.description())))
+                       .appendSkipNull("externalDocs", 
merge(om.getMap("externalDocs"), toMap(a.externalDocs(), location, 
locationArgs)));
+               return nullIfEmpty(om);
+       }
+
+       private OList toList(Tag[] aa, String location, Object...locationArgs) 
throws ParseException {
+               if (aa.length == 0)
+                       return null;
+               OList ol = new OList();
+               for (Tag a : aa)
+                       ol.add(toMap(a, location, locationArgs));
+               return nullIfEmpty(ol);
+       }
+
+       private OMap getSchema(OMap schema, Type type, BeanSession bs) throws 
Exception {
+
+               if (type == Swagger.class)
+                       return null;
+
+               schema = newMap(schema);
+
+               ClassMeta<?> cm = bs.getClassMeta(type);
+
+               if (schema.getBoolean("ignore", false))
+                       return null;
+
+               if (schema.containsKey("type") || schema.containsKey("$ref"))
+                       return schema;
+
+               OMap om = fixSwaggerExtensions(schema.append(js.getSchema(cm)));
+
+               return nullIfEmpty(om);
+       }
+
+       /**
+        * Replaces non-standard JSON-Schema attributes with standard Swagger 
attributes.
+        */
+       private OMap fixSwaggerExtensions(OMap om) {
+               om
+                       .appendSkipNull("discriminator", 
om.remove("x-discriminator"))
+                       .appendSkipNull("readOnly", om.remove("x-readOnly"))
+                       .appendSkipNull("xml", om.remove("x-xml"))
+                       .appendSkipNull("externalDocs", 
om.remove("x-externalDocs"))
+                       .appendSkipNull("example", om.remove("x-example"));
+               return nullIfEmpty(om);
+       }
+
+       private void addBodyExamples(RestMethodContext sm, OMap piri, boolean 
response, Type type, Locale locale) throws Exception {
+
+               String sex = piri.getString("x-example");
+
+               if (sex == null) {
+                       OMap schema = resolveRef(piri.getMap("schema"));
+                       if (schema != null)
+                               sex = schema.getString("example", 
schema.getString("x-example"));
+               }
+
+               if (isEmpty(sex))
+                       return;
+
+               Object example = null;
+               if (isJson(sex)) {
+                       example = jp.parse(sex, type);
+               } else {
+                       ClassMeta<?> cm = js.getClassMeta(type);
+                       if (cm.hasStringMutater()) {
+                               example = cm.getStringMutater().mutate(sex);
+                       }
+               }
+
+               String examplesKey = response ? "examples" : "x-examples";  // 
Parameters don't have an examples attribute.
+
+               OMap examples = piri.getMap(examplesKey);
+               if (examples == null)
+                       examples = new OMap();
+
+               List<MediaType> mediaTypes = response ? 
sm.getSerializers().getSupportedMediaTypes() : 
sm.getParsers().getSupportedMediaTypes();
+
+               for (MediaType mt : mediaTypes) {
+                       if (mt != MediaType.HTML) {
+                               Serializer s2 = 
sm.getSerializers().getSerializer(mt);
+                               if (s2 != null) {
+                                       SerializerSessionArgs args =
+                                               SerializerSessionArgs
+                                                       .create()
+                                                       .locale(locale)
+                                                       .mediaType(mt)
+                                                       .useWhitespace(true)
+                                               ;
+                                       try {
+                                               String eVal = 
s2.createSession(args).serializeToString(example);
+                                               
examples.put(s2.getPrimaryMediaType().toString(), eVal);
+                                       } catch (Exception e) {
+                                               System.err.println("Could not 
serialize to media type ["+mt+"]: " + e.getLocalizedMessage());  // NOT DEBUG
+                                       }
+                               }
+                       }
+               }
+
+               if (! examples.isEmpty())
+                       piri.put(examplesKey, examples);
+       }
+
+       private void addParamExample(RestMethodContext sm, OMap piri, 
RestParamType in, Type type) throws Exception {
+
+               String s = piri.getString("x-example");
+
+               if (isEmpty(s))
+                       return;
+
+               OMap examples = piri.getMap("x-examples");
+               if (examples == null)
+                       examples = new OMap();
+
+               String paramName = piri.getString("name");
+
+               if (in == QUERY)
+                       s = "?" + urlEncodeLax(paramName) + "=" + 
urlEncodeLax(s);
+               else if (in == FORM_DATA)
+                       s = paramName + "=" + s;
+               else if (in == HEADER)
+                       s = paramName + ": " + s;
+               else if (in == PATH)
+                       s = sm.getPathPattern().replace("{"+paramName+"}", 
urlEncodeLax(s));
+
+               examples.put("example", s);
+
+               if (! examples.isEmpty())
+                       piri.put("x-examples", examples);
+       }
+
+
+       private OMap resolveRef(OMap m) {
+               if (m == null)
+                       return null;
+               if (m.containsKey("$ref") && js.getBeanDefs() != null) {
+                       String ref = m.getString("$ref");
+                       if (ref.startsWith("#/definitions/"))
+                               return js.getBeanDefs().get(ref.substring(14));
+               }
+               return m;
+       }
+
+       private OMap getOperation(OMap om, String path, String httpMethod) {
+               if (! om.containsKey("paths"))
+                       om.put("paths", new OMap());
+               om = om.getMap("paths");
+               if (! om.containsKey(path))
+                       om.put(path, new OMap());
+               om = om.getMap(path);
+               if (! om.containsKey(httpMethod))
+                       om.put(httpMethod, new OMap());
+               return om.getMap(httpMethod);
+       }
+
+       private static OMap newMap(OMap om) {
+               if (om == null)
+                       return new OMap();
+               return om.modifiable();
+       }
+
+       private OMap merge(OMap om, Body a) throws ParseException {
+               if (BodyAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.value().length > 0)
+                       om.putAll(parseMap(a.value()));
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipEmpty("x-examples", parseMap(a.examples()), 
parseMap(a.exs()))
+                       .appendSkipFalse("required", a.required() || a.r())
+                       .appendSkipEmpty("schema", merge(om.getMap("schema"), 
a.schema()))
+               ;
+       }
+
+       private OMap merge(OMap om, Query a) throws ParseException {
+               if (QueryAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipFalse("allowEmptyValue", a.allowEmptyValue() 
|| a.aev())
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipFalse("required", a.required() || a.r())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+               ;
+       }
+
+       private OMap merge(OMap om, FormData a) throws ParseException {
+               if (FormDataAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipFalse("allowEmptyValue", a.allowEmptyValue() 
|| a.aev())
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipFalse("required", a.required())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+               ;
+       }
+
+       private OMap merge(OMap om, Header a) throws ParseException {
+               if (HeaderAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipFalse("required", a.required() || a.r())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+               ;
+       }
+
+       private OMap merge(OMap om, Path a) throws ParseException {
+               if (PathAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+               ;
+       }
+
+       private OMap merge(OMap om, Schema a) throws ParseException {
+               if (SchemaAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.value().length > 0)
+                       om.putAll(parseMap(a.value()));
+               return om
+                       .appendSkipEmpty("additionalProperties", 
toOMap(a.additionalProperties()))
+                       .appendSkipEmpty("allOf", joinnl(a.allOf()))
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("discriminator", a.discriminator())
+                       .appendSkipEmpty("description", 
resolve(a.description()), resolve(a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example()), 
resolve(a.ex()))
+                       .appendSkipEmpty("examples", parseMap(a.examples()), 
parseMap(a.exs()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("externalDocs", 
merge(om.getMap("externalDocs"), a.externalDocs()))
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("ignore", a.ignore() ? "true" : null)
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipMinusOne("maxProperties", a.maxProperties(), 
a.maxp())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipMinusOne("minProperties", a.minProperties(), 
a.minp())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipEmpty("properties", toOMap(a.properties()))
+                       .appendSkipFalse("readOnly", a.readOnly() || a.ro())
+                       .appendSkipFalse("required", a.required() || a.r())
+                       .appendSkipEmpty("title", a.title())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+                       .appendSkipEmpty("xml", joinnl(a.xml()))
+                       .appendSkipEmpty("$ref", a.$ref())
+               ;
+       }
+
+       private OMap merge(OMap om, ExternalDocs a) throws ParseException {
+               if (ExternalDocsAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.value().length > 0)
+                       om.putAll(parseMap(a.value()));
+               return om
+                       .appendSkipEmpty("description", 
resolve(a.description()))
+                       .appendSkipEmpty("url", a.url())
+               ;
+       }
+
+       private OMap merge(OMap om, Items a) throws ParseException {
+               if (ItemsAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.value().length > 0)
+                       om.putAll(parseMap(a.value()));
+               return om
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipEmpty("$ref", a.$ref())
+               ;
+       }
+
+       private OMap merge(OMap om, SubItems a) throws ParseException {
+               if (SubItemsAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.value().length > 0)
+                       om.putAll(parseMap(a.value()));
+               return om
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", toOMap(a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+                       .appendSkipEmpty("$ref", a.$ref())
+               ;
+       }
+
+       private OMap merge(OMap om, Response a) throws ParseException {
+               if (ResponseAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipEmpty("examples", parseMap(a.examples()), 
parseMap(a.exs()))
+                       .appendSkipEmpty("headers", merge(om.getMap("headers"), 
a.headers()))
+                       .appendSkipEmpty("schema", merge(om.getMap("schema"), 
a.schema()))
+               ;
+       }
+
+       private OMap merge(OMap om, ResponseHeader[] a) throws ParseException {
+               if (a.length == 0)
+                       return om;
+               om = newMap(om);
+               for (ResponseHeader aa : a) {
+                       String name = StringUtils.firstNonEmpty(aa.name(), 
aa.value());
+                       if (isEmpty(name))
+                               throw new RuntimeException("@ResponseHeader 
used without name or value.");
+                       om.getMap(name, true).putAll(merge(null, aa));
+               }
+               return om;
+       }
+
+       private OMap merge(OMap om, ResponseHeader a) throws ParseException {
+               if (ResponseHeaderAnnotation.empty(a))
+                       return om;
+               om = newMap(om);
+               if (a.api().length > 0)
+                       om.putAll(parseMap(a.api()));
+               return om
+                       .appendSkipEmpty("collectionFormat", 
a.collectionFormat(), a.cf())
+                       .appendSkipEmpty("default", joinnl(a._default(), 
a.df()))
+                       .appendSkipEmpty("description", 
resolve(a.description(), a.d()))
+                       .appendSkipEmpty("enum", toSet(a._enum()), toSet(a.e()))
+                       .appendSkipEmpty("x-example", resolve(a.example(), 
a.ex()))
+                       .appendSkipFalse("exclusiveMaximum", 
a.exclusiveMaximum() || a.emax())
+                       .appendSkipFalse("exclusiveMinimum", 
a.exclusiveMinimum() || a.emin())
+                       .appendSkipEmpty("format", a.format(), a.f())
+                       .appendSkipEmpty("items", merge(om.getMap("items"), 
a.items()))
+                       .appendSkipEmpty("maximum", a.maximum(), a.max())
+                       .appendSkipMinusOne("maxItems", a.maxItems(), a.maxi())
+                       .appendSkipMinusOne("maxLength", a.maxLength(), 
a.maxl())
+                       .appendSkipEmpty("minimum", a.minimum(), a.min())
+                       .appendSkipMinusOne("minItems", a.minItems(), a.mini())
+                       .appendSkipMinusOne("minLength", a.minLength(), 
a.minl())
+                       .appendSkipEmpty("multipleOf", a.multipleOf(), a.mo())
+                       .appendSkipEmpty("pattern", a.pattern(), a.p())
+                       .appendSkipEmpty("type", a.type(), a.t())
+                       .appendSkipFalse("uniqueItems", a.uniqueItems() || 
a.ui())
+                       .appendSkipEmpty("$ref", a.$ref())
+               ;
+       }
+
+       private OMap mergePartSchema(OMap param, OMap schema) {
+               if (schema != null) {
+                       param
+                               .appendIf(false, true, true, 
"collectionFormat", schema.remove("collectionFormat"))
+                               .appendIf(false, true, true, "default", 
schema.remove("default"))
+                               .appendIf(false, true, true, "description", 
schema.remove("enum"))
+                               .appendIf(false, true, true, "enum", 
schema.remove("enum"))
+                               .appendIf(false, true, true, "x-example", 
schema.remove("x-example"))
+                               .appendIf(false, true, true, 
"exclusiveMaximum", schema.remove("exclusiveMaximum"))
+                               .appendIf(false, true, true, 
"exclusiveMinimum", schema.remove("exclusiveMinimum"))
+                               .appendIf(false, true, true, "format", 
schema.remove("format"))
+                               .appendIf(false, true, true, "items", 
schema.remove("items"))
+                               .appendIf(false, true, true, "maximum", 
schema.remove("maximum"))
+                               .appendIf(false, true, true, "maxItems", 
schema.remove("maxItems"))
+                               .appendIf(false, true, true, "maxLength", 
schema.remove("maxLength"))
+                               .appendIf(false, true, true, "minimum", 
schema.remove("minimum"))
+                               .appendIf(false, true, true, "minItems", 
schema.remove("minItems"))
+                               .appendIf(false, true, true, "minLength", 
schema.remove("minLength"))
+                               .appendIf(false, true, true, "multipleOf", 
schema.remove("multipleOf"))
+                               .appendIf(false, true, true, "pattern", 
schema.remove("pattern"))
+                               .appendIf(false, true, true, "required", 
schema.remove("required"))
+                               .appendIf(false, true, true, "type", 
schema.remove("type"))
+                               .appendIf(false, true, true, "uniqueItems", 
schema.remove("uniqueItems"));
+
+                       if ("object".equals(param.getString("type")) && ! 
schema.isEmpty())
+                               param.put("schema", schema);
+               }
+
+               return param;
+       }
+
+
+
+       private OMap toOMap(String[] ss) throws ParseException {
+               if (ss.length == 0)
+                       return null;
+               String s = joinnl(ss);
+               if (s.isEmpty())
+                       return null;
+               if (! isJsonObject(s, true))
+                       s = "{" + s + "}";
+               s = resolve(s);
+               return OMap.ofJson(s);
+       }
+
+       private Set<String> toSet(String[] ss) throws ParseException {
+               if (ss.length == 0)
+                       return null;
+               String s = joinnl(ss);
+               if (s.isEmpty())
+                       return null;
+               s = resolve(s);
+               Set<String> set = ASet.of();
+               for (Object o : StringUtils.parseListOrCdl(s))
+                       set.add(o.toString());
+               return set;
+       }
+
+       static String joinnl(String[]...s) {
+               for (String[] ss : s) {
+                       if (ss.length != 0)
+                       return StringUtils.joinnl(ss).trim();
+               }
+               return "";
+       }
+
+       private static Set<Integer> getCodes(List<Response> la, Integer def) {
+               Set<Integer> codes = new TreeSet<>();
+               for (Response a : la) {
+                       for (int i : a.value())
+                               codes.add(i);
+                       for (int i : a.code())
+                               codes.add(i);
+               }
+               if (codes.isEmpty() && def != null)
+                       codes.add(def);
+               return codes;
+       }
+
+       private static Set<Integer> getCodes2(List<ResponseHeader> la, Integer 
def) {
+               Set<Integer> codes = new TreeSet<>();
+               for (ResponseHeader a : la) {
+                       for (int i : a.code())
+                               codes.add(i);
+               }
+               if (codes.isEmpty() && def != null)
+                       codes.add(def);
+               return codes;
+       }
+
+       private static OMap nullIfEmpty(OMap m) {
+               return (m == null || m.isEmpty() ? null : m);
+       }
+
+       private static OList nullIfEmpty(OList l) {
+               return (l == null || l.isEmpty() ? null : l);
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
new file mode 100644
index 0000000..f8d89f6
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
@@ -0,0 +1,115 @@
+// 
***************************************************************************************************************************
+// * 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 org.apache.juneau.cp.*;
+import org.apache.juneau.http.exception.*;
+import org.apache.juneau.jsonschema.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Builder class for {@link SwaggerProvider} objects.
+ */
+public class SwaggerProviderBuilder {
+
+       private Class<? extends SwaggerProvider> implClass;
+
+       BeanFactory beanFactory;
+       Class<?> resourceClass;
+       VarResolver varResolver;
+       JsonSchemaGenerator jsonSchemaGenerator;
+       Messages messages;
+       FileFinder fileFinder;
+
+       /**
+        * Creates a new {@link SwaggerProvider} object from this builder.
+        *
+        * @return A new {@link SwaggerProvider} object.
+        */
+       public SwaggerProvider build() {
+               try {
+                       Class<? extends SwaggerProvider> ic = implClass == null 
? SwaggerProvider.class : implClass;
+                       return 
BeanFactory.of(beanFactory).addBean(SwaggerProviderBuilder.class, 
this).createBean(ic);
+               } catch (Exception e) {
+                       throw toHttpException(e, InternalServerError.class);
+               }
+       }
+
+       /**
+        * Specifies the bean factory to use for instantiating the {@link 
SwaggerProvider} object.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder beanFactory(BeanFactory value) {
+               this.beanFactory = value;
+               return this;
+       }
+
+       /**
+        * Specifies the variable resolver to use for the {@link 
SwaggerProvider} object.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder varResolver(VarResolver value) {
+               this.varResolver = value;
+               return this;
+       }
+
+       /**
+        * Specifies the JSON-schema generator to use for the {@link 
SwaggerProvider} object.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder jsonSchemaGenerator(JsonSchemaGenerator 
value) {
+               this.jsonSchemaGenerator = value;
+               return this;
+       }
+
+       /**
+        * Specifies the messages to use for the {@link SwaggerProvider} object.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder messages(Messages value) {
+               this.messages = value;
+               return this;
+       }
+
+       /**
+        * Specifies the file-finder to use for the {@link SwaggerProvider} 
object.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder fileFinder(FileFinder value) {
+               this.fileFinder = value;
+               return this;
+       }
+
+       /**
+        * Specifies a subclass of {@link SwaggerProvider} to create when the 
{@link #build()} method is called.
+        *
+        * @param value The new value for this setting.
+        * @return  This object (for method chaining).
+        */
+       public SwaggerProviderBuilder implClass(Class<? extends 
SwaggerProvider> value) {
+               this.implClass = value;
+               return this;
+       }
+}

Reply via email to