# RestHandler.java

    /*
    ** Module   : RestHandler.java
    ** Abstract : Handler for REST calls.
    **
    ** Copyright (c) 2019-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -----------------------------------------------Description--------------------------------
    ** 001 CA  20190611 First version.
    ** 002 CA  20190628 Proper SINGLETON and SINGLE-RUN implementation; misc improvements.
    **     CA  20190703 Extracted common code to LegacyServiceHandler.
    ** 003 CA  20190723 The path matching is done case-insensitive.
    ** 004 CA  20200427 Fixed to allow session context details.
    **                  Added application/x-www-form-urlencoded support.
    **     CA  20200514 Refactoring to allow common code for SOAP services support.
    ** 005 CA  20200910 The 'address' at the service signature is included in the key.
    **     CA  20210227 Each specific 'address' must have its own handler, so the context path can be reported
    **                  and managed properly.
    **     CA  20211112 Fixed some problems resetting the last invoke configuration.
    **                  Fixed setting the status - it must be set before the response body is sent.
    **     CA  20220329 Added support for REST services written directly in Java.
    **     CA  20220405 Added authentication and authorization for web requests.  When this is enabled, the target
    **                  API call will be executed under the authenticated FWD context, and not the agent's context.
    **     SBI 20221215 Changed buildServiceKey to provide parameters types as new parameter for RestServiceKey.
    **     SBI 20221216 Choose the target rest service by comparing its context path with a request context path.
    **     SBI 20221229 Reduced the number of iterations until the target request handler would be found.
    ** 006 SBI 20230216 Fixed buildJavaParameter(..) because defaultValue() was added to LegacyServiceParameter,
    **                  changed invokeRestService,...) to handle RestException.
    ** 007 SBI 20230324 Fixed legacy rest services to be added to the rest service context map.
    ** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 009 GBB 20240610 LegacyAppServerWork replaced by Consumer<LegacyServiceWorker>.
    ** 010 SBI 20231206 Resolved conflicting rest service methods with help of the priority queue.
    ** 011 GBB 20250403 Replace service type with enum constant.
    ** 012 RB2 20250530 The handle method now calls the handle method with an extra parameter, the request. This
    **                  was done in order to handle multiple URLs in the Access-Control-Allow-Origin field
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.rest;

    import java.io.*;
    import java.lang.annotation.*;
    import java.lang.reflect.*;
    import java.lang.reflect.Parameter;
    import java.util.*;
    import java.util.function.*;
    import java.util.logging.*;

    import javax.servlet.http.*;

    import com.goldencode.p2j.util.logging.*;
    import org.eclipse.jetty.http.*;
    import org.eclipse.jetty.server.*;
    import org.eclipse.jetty.server.handler.*;

    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.util.*;

    /**
     * Processes the REST requests and delegates the calls to the mapped service.
     * <p>
     * Service registration is being performed via {@link SourceNameMapper}, which loads the
     * programs marked with <code>with-services</code>, via {@link #addService} API.
     * <p>
     * Any converted Java code mapped to a service must have a class-level {@link LegacyService}
     * annotation.  If this has operations defined via internal entries (procedure, function, method),
     * then each Java method must be annotated via {@link LegacyService}.
     * <p>
     * The implicit basepath is <code>/rest</code>, and the implicit timeout is 10 seconds (set to 0
     * to disable it).  This timeout is read from the appserver's timeout.
     * <p>
     * The values which need to be configured in the directory are:
     * <ul>
     *    <li><code>rest/basepath</code>, to specify the basepath.  Defaults to <code>/rest</code>.</li>
     *    <li><code>rest/alias</code>, a process certificate alias associated with the workers which
     *        will process the REST requests; these will delegate the call to the appserver.  This
     *        context will automatically be set as HEADLESS.</li>
     *    <li><code>rest/keyStore</code>, the key store with the certificate to authenticate this
     *        process.  If is missing, the store will be built from the directory.</li>
     *    <li><code>rest/storePassword</code>, the store password, only if <code>rest/keyStore</code>
     *        is set.</li>
     *    <li><code>rest/poolSize</code> - the pool size of workers.  Defaults to 10.</li>
     *    <li><code>rest/appserver</code> - the name of the appserver handling the requests.</li>
     * </ul>
     * <p>
     * Each queue of REST workers is ordered by the number of pending tasks to be executed - for each
     * incoming request, the worker with the smallest number of pending tasks will be chosen.
     */
    public class RestHandler
    extends LegacyWebServiceHandler
    {
       /** The singleton instance for this handler. */
       private static RestHandler instance = null;

       /** Anonymous log instance. */
       private static final CentralLogger LOG = CentralLogger.get(RestHandler.class.getName());

       /** A mapping of mime-types to their request resolvers. */
       private static Map<String, Supplier<? extends RequestArguments>> requests = new HashMap<>();

       /** A mapping of mime-types to their response serializers. */
       private static Map<String, Supplier<? extends ResponseArguments>> responses = new HashMap<>();

       /** The context handler. */
       private static ContextHandlerCollection handler;

       /** The already registered addresses. */
       private Set<String> addresses = new HashSet<>();

       /** Map context path to rest services registered with the same context path */
       private Map<String, Map<ServiceKey, RestService>> restServices = new HashMap<>();

       /**
        * Initialize this handler with the 'rest' type and '/rest' default basepath.
        */
       public RestHandler()
       {
          super(ServiceType.REST, "/rest");
       }

       /**
        * Determine if this web service is configured to authenticate the APIs.
        *
        * @return   <code>true</code> if the authentication is enabled for this web service, in the directory.
        */
       public static boolean useWebServiceAuthentication()
       {
          synchronized (RestHandler.class)
          {
             return instance != null && instance.useAuthorization();
          }
       }

       /**
        * Terminate the rest worker threads.
        */
       public static void terminate()
       {
          synchronized (RestHandler.class)
          {
             if (instance != null)
             {
                instance.terminateWorkers();
             }
          }
       }

       /**
        * Initialize the REST worker(s) and start the workers.
        *
        * @return   The handler for REST services.
        */
       public static AbstractHandler initialize()
       {
          synchronized (RestHandler.class)
          {
             if (handler != null)
             {
                return handler;
             }

             instance = new RestHandler();
             instance.initializeHandler();

             requests.put("application/x-www-form-urlencoded", () -> new BaseRequestsParser());
             requests.put("application/json", () -> new JsonRequestsParser());
             responses.put("application/json", () -> new JsonResponseArguments());

             handler = new ContextHandlerCollection();

             return handler;
          }
       }

       /**
        * Register a service which is defined in Java, and does not adhere to the constraints of a legacy program.
        *
        * @param    ls
        *           The annotation describing the service, at the defining class.
        * @param    cls
        *           The Java class defining this service.
        * @param    mls
        *           The annotation describing the service, at the Java method.
        * @param    m
        *           The target Java method.
        */
       public static void addJavaService(LegacyService ls,
                                         Class<?>      cls,
                                         LegacyService mls,
                                         Method        m)
       {
          synchronized (RestHandler.class)
          {
             if (instance == null)
             {
                LOG.warning("Trying to register REST service " + mls.name() +
                                   ", but REST is not enabled.");
                return;
             }
          }

          instance.registerJavaService(ls, cls, mls, m);
       }

       /**
        * Register a legacy service associated with a legacy OO class method.
        *
        * @param    ls
        *           The annotation describing the service, at the legacy class.
        * @param    cls
        *           The {@link com.goldencode.p2j.oo.lang.BaseObject} sub-class implementing this
        *           service.
        * @param    mls
        *           The annotation describing the service, at the legacy method.
        * @param    m
        *           The target operation.
        */
       public static void addService(LegacyService                 ls,
                                     Class<? extends _BaseObject_> cls,
                                     LegacyService                 mls,
                                     Method                        m)
       {
          synchronized (RestHandler.class)
          {
             if (instance == null)
             {
                LOG.warning("Trying to register REST service " + mls.name() +
                                   ", but REST is not enabled.");
                return;
             }
          }

          RestService srv = instance.registerService(ls, cls, mls, m);
          instance.addToContextMap(srv);
       }

       /**
        * Register a legacy service associated with a legacy external program.
        *
        * @param    ls
        *           The annotation describing the service.
        * @param    pname
        *           The legacy program name.
        * @param    m
        *           The target Java method for this service.
        */
       public static void addService(LegacyService ls, String pname, Method m)
       {
          synchronized (RestHandler.class)
          {
             if (instance == null)
             {
                LOG.warning("Trying to register REST service " + pname +
                                   ", but REST is not enabled.");
                return;
             }
          }

          RestService srv = instance.registerService(ls, pname, m);
          instance.addToContextMap(srv);
       }

       /**
        * Register a legacy service associated with an internal entry for an external program.
        *
        * @param    ls
        *           The annotation describing the service, at the external program.
        * @param    pname
        *           The legacy program name.
        * @param    iels
        *           The annotation describing the service, at the internal entry.
        * @param    ie
        *           The target internal entry for this service.
        * @param    function
        *           Flag identifying if the internal entry is a function.
        */
       public static void addService(LegacyService  ls,
                                     String         pname,
                                     LegacyService  iels,
                                     InternalEntry  ie,
                                     boolean        function)
       {
          synchronized (RestHandler.class)
          {
             if (instance == null)
             {
                LOG.warning("Trying to register REST service " + pname + "." + ie.getLegacyName() +
                                   ", but REST is not enabled.");
                return;
             }
          }

          RestService srv = instance.registerService(ls, pname, iels, ie, function);
          instance.addToContextMap(srv);
       }

       /**
        * From a structure like <code>a.attr['a']</code>, extract the bracketed value and remove the
        * quotes.
        *
        * @param    src
        *           The source text.
        *
        * @return   The bracketed value.
        */
       static String extractVal(String src)
       {
          String val = src.substring(src.indexOf('[') + 1);
          val = val.substring(0, val.lastIndexOf(']'));
          if (val.startsWith("'") || val.startsWith("\""))
          {
             val = val.substring(1, val.length() - 1);
          }

          return val;
       }

       /**
        * Execute the REST request.
        *
        * @param    rest
        *           The REST service instance.
        * @param    token
        *           When not null, it represents the token of a FWD context created for an authenticated and
        *           authorized web service call.  The API call will be performed in that context.
        * @param    worker
        *           The REST worker.
        * @param    target
        *           The target path.
        * @param    request
        *           The HTTP servlet request instance.
        * @param    response
        *           The HTTP servlet response instance.
        */
       private static void invoke(RestService         rest,
                                  String              token,
                                  LegacyServiceWorker worker,
                                  String              target,
                                  HttpServletRequest  request,
                                  HttpServletResponse response)
       throws IOException
       {
          OutputStream stream = response.getOutputStream();
          response.setContentType(rest.ls.produces());

          RequestArguments rs = requests.get(rest.ls.consumes()).get();
          ResponseArguments rw = responses.get(rest.ls.produces()).get();

          try
          {
             try
             {
                AppServerHelper helper = worker.getHelper();

                Object[] args = rs.parseArguments(rest, request);

                Object retVal = rest.invoke(helper, token, args);

                rw.writeArguments(retVal, args, rest, stream, response);
             }
             catch (Throwable exc)
             {
                LOG.log(Level.SEVERE, "Could not execute " + target, exc);

                Throwable t = exc;
                while (t != null)
                {
                   if (t instanceof LegacyErrorException)
                   {
                      LegacyErrorException lex = (LegacyErrorException) t;
                      LegacyError error = lex.getErrorRef();

                      response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);

                      // write the error to JSON
                      String json = rw.writeError(error);
                      stream.write(json.getBytes());
                      return;
                   }
                   else if (t instanceof RestException)
                   {
                      response.setStatus(((RestException) t).getStatus());
                      t.printStackTrace(new PrintStream(stream));
                      return;
                   }
                   if (t instanceof ErrorConditionException)
                   {
                      if (t.getCause() instanceof StopConditionException)
                      {
                         throw (StopConditionException) t.getCause();
                      }
                      else
                      {
                         // String msg = "ERROR condition: " + t.getMessage();
                         response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                         String msg = "ERROR condition: The Server application has returned an error. (7243) (7211)";
                         stream.write(msg.getBytes());
                      }
                      return;
                   }
                   else if (t instanceof ConditionException)
                   {
                      throw (ConditionException) t;
                   }
                   else if (t instanceof RequestArgumentError)
                   {
                      throw (RequestArgumentError) t;
                   }

                   t = t.getCause();
                }

                // other failure?
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                stream.write(exc.getMessage().getBytes());
             }
             finally
             {
                // reset the config
                rest.reset();
             }
          }
          catch (QuitConditionException |
                 StopConditionException e)
          {
             response.setStatus(HttpStatus.GATEWAY_TIMEOUT_504);
          }
          catch (ConditionException e)
          {
             response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
             stream.write(e.getMessage().getBytes());
          }
          catch (RequestArgumentError e)
          {
             response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
             stream.write(e.getMessage().getBytes());
          }
       }

       /**
        * Code to process web requests.
        */
       @Override
       public void handle(String              target,
                          Request             baseRequest,
                          HttpServletRequest  request,
                          HttpServletResponse response)
       throws IOException
       {
          if (baseRequest.isHandled())
          {
             return;
          }

          // first check the authentication
          super.handle(target, baseRequest, request, response);

          if (baseRequest.isHandled())
          {
             // authentication failed, nothing else to do
             return;
          }

          baseRequest.setHandled(true);

          String[] targetPaths = getPaths(target);

          String method = request.getMethod();
          PriorityQueue<RestServiceKey> candidates = new PriorityQueue<RestServiceKey>(new Comparator<RestServiceKey>()
          {
             public int compare(RestServiceKey o1, RestServiceKey o2)
             {
                int size = Math.min(o1.mask.size(), o2.mask.size());
                for(int i = 0; i < size; i++)
                {
                   if (o1.mask.get(i) != o2.mask.get(i))
                   {
                      if (o1.mask.get(i))
                      {
                         return -i - 1;
                      }
                      else
                      {
                         return i + 1;
                      }
                   }
                }

                return 0;
             }
          });
          Map<ServiceKey, RestService> contextServices = restServices.getOrDefault(request.getContextPath(), Collections.EMPTY_MAP);

          for (ServiceKey skey : contextServices.keySet())
          {
             RestServiceKey key = (RestServiceKey) skey;
             if (method.equals(key.verb))
             {
                if (key.paths.length == targetPaths.length)
                {
                   boolean match = true;
                   BitSet mask = key.mask;
                   // iterate over all none wildcards elements in the rest path
                   for (int i = mask.nextSetBit(0); i >= 0; i = mask.nextSetBit(i + 1))
                   {
                      String p = key.paths[i];
                      if (!p.equalsIgnoreCase(targetPaths[i]))
                      {
                         match = false;
                         break;
                      }
                      // operate on index i here
                      if (i == Integer.MAX_VALUE)
                      {
                         break; // or (i+1) would overflow
                      }
                   }

                   if (match)
                   {
                      candidates.add(key);
                   }
                }
             }
          }

          if (candidates.isEmpty())
          {
             // TODO: we need to differentiate between a service configured in the .paar definition,
             // and a real missing program.
             response.setStatus(HttpStatus.NOT_FOUND_404);
             response.getOutputStream().write(("Can't find service for path " + target).getBytes());
             return;
          }

          RestService rest = contextServices.get(candidates.peek());
          LegacyAppServerWork work = (worker) ->
          {
             // now do the authorization - must be in the worker thread
             String token;
             if (useAuthorization())
             {
                token = authorize(target, request, response);
                if (token == null)
                {
                   return;
                }
             }
             else
             {
                token = null;
             }

             try
             {
                invoke(rest, token, worker, target, request, response);
             }
             catch (IOException e)
             {
                throw new RuntimeException(e);
             }
             finally
             {
                postHandle(token);
             }
          };

          handle(this, work, target, response, request);
       }

       /**
        * Build a {@link RestServiceKey} for the specified legacy service.
        *
        * @param    ls
        *           The legacy service annotation.
        *
        * @return   The service key.
        */
       @Override
       protected RestServiceKey buildServiceKey(LegacyService ls)
       {
          java.util.stream.Stream<LegacyServiceParameter> legacyParametersStream = Arrays.stream(ls.parameters());
          List<String> typesList = new ArrayList<String>(ls.parameters().length);
          legacyParametersStream.map(lp -> {return lp.type();}).forEach(tp -> typesList.add(tp));
          RestServiceKey rsk = new RestServiceKey(this.basePath, ls.address(), ls.path(), ls.verb(), typesList);
          if (!addresses.contains(rsk.address))
          {
             addresses.add(rsk.address);

             ContextHandler keyHandler = new ContextHandler();
             String path = getBasePath() + rsk.address;
             while (path.indexOf("//") >= 0)
             {
                path = path.replaceAll("//", "/");
             }
             if (path.endsWith("/"))
             {
                path = path.substring(0, path.length() - 1);
             }
             keyHandler.setContextPath(path);
             keyHandler.setHandler(this);

             handler.addHandler(keyHandler);
          }

          return rsk;
       }

       /**
        * Register a service which is defined in Java, and does not adhere to the constraints of a legacy program.
        *
        * @param    ls
        *           The annotation describing the service, at the defining class.
        * @param    cls
        *           The Java class defining this service.
        * @param    mls
        *           The annotation describing the service, at the Java method.
        * @param    m
        *           The target Java method.
        */
       private void registerJavaService(LegacyService ls,
                                        Class<?>      cls,
                                        LegacyService mls,
                                        Method        m)
       {
          // re-build the parameters and method's service annotation, by:
          // - inheriting any 'address', 'produces', 'consumes' set at the class
          // - getting the argument name and type from the Java method's signature
          BiFunction<LegacyService, LegacyServiceParameter[], LegacyService> buildService = (orig, params) ->
          {
             return new LegacyService()
             {
                @Override
                public Class<? extends Annotation> annotationType()
                {
                   return LegacyService.class;
                }

                @Override
                public String type()
                {
                   return orig.type();
                }

                @Override
                public LegacyWebPath[] paths()
                {
                   return orig.paths();
                }

                @Override
                public String namespace()
                {
                   return orig.namespace();
                }

                @Override
                public int order()
                {
                   return orig.order();
                }

                @Override
                public String executionMode()
                {
                   return orig.executionMode();
                }

                @Override
                public String name()
                {
                   return orig.name();
                }

                @Override
                public String path()
                {
                   return orig.path();
                }

                @Override
                public String address()
                {
                   return orig.address().isEmpty() ? ls.address() : orig.address();
                }

                @Override
                public String verb()
                {
                   return orig.verb();
                }

                @Override
                public String produces()
                {
                   return orig.produces().isEmpty() ? ls.produces() : orig.produces();
                }

                @Override
                public String consumes()
                {
                   return orig.consumes().isEmpty() ? ls.consumes() : orig.consumes();
                }

                @Override
                public LegacyServiceParameter[] parameters()
                {
                   return params;
                }

                @Override
                public String binding()
                {
                   return orig.binding();
                }
             };
          };

          Type retType = m.getGenericReturnType();
          Parameter[] params = m.getParameters();
          LegacyServiceParameter[] defParams = mls.parameters();
          LegacyServiceParameter retParam = null;
          LegacyServiceParameter[] newParams = new LegacyServiceParameter[params.length];

          for (int i = 0; i < defParams.length; i++)
          {
             LegacyServiceParameter lsp = defParams[i];

             if ((lsp.output() || lsp.returnValue()) && lsp.target().isEmpty())
             {
                throw new IllegalStateException("Parameter " + lsp.ordinal() + " in " + m.toString() +
                                                " is marked OUTPUT, but no TARGET is set.");
             }
             if (lsp.input() && lsp.source().isEmpty())
             {
                throw new IllegalStateException("Parameter " + lsp.ordinal() + " in " + m.toString() +
                                                " is marked INPUT, but no SOURCE is set.");
             }
             if (lsp.input() && !lsp.output() && !lsp.target().isEmpty())
             {
                throw new IllegalStateException("Parameter " + lsp.ordinal() + " in " + m.toString() +
                                                " is marked INPUT, and TARGET is set.");
             }
             if ((lsp.output() || lsp.returnValue()) && !lsp.input() && !lsp.source().isEmpty())
             {
                throw new IllegalStateException("Parameter " + lsp.ordinal() + " in " + m.toString() +
                                                " is marked OUTPUT, and SOURCE is set.");
             }

             if (lsp.returnValue())
             {
                retParam = buildJavaParameter(lsp, retType, "retVal");
                continue;
             }

             int idx = lsp.ordinal() - 1;
             Type ptype = params[idx].getParameterizedType();
             String pname = params[idx].getName();
             newParams[idx] = buildJavaParameter(lsp, ptype, pname);
          }

          if (retParam != null && (retType == void.class || retType == Void.class))
          {
             throw new RuntimeException("'returnValue = true' parameter can't be used for method " + m.getName() +
                                        " in class " + cls.getName() + ", which has a 'void' return type!");
          }
          for (int i = 0; i < newParams.length; i++)
          {
             if (newParams[i] == null)
             {
                throw new RuntimeException("Parameter " + (i + 1) + " at the " + m.getName() + " in " +
                                           cls.getName() + " must be defined at the LegacyService!");
             }
          }

          if (retParam != null)
          {
             LegacyServiceParameter[] withRetVal = new LegacyServiceParameter[params.length + 1];
             withRetVal[0] = retParam;
             System.arraycopy(newParams, 0, withRetVal, 1, params.length);
             newParams = withRetVal;
          }

          LegacyService newService = buildService.apply(mls, newParams);

          // create a JavaRestService and set at it:
          // - the target class
          // - the target method
          // - the method's LegacySignature
          // - the method's return LegacyServiceParameter
          RestServiceKey key = buildServiceKey(newService);

          if (services.containsKey(key))
          {
             throw new RuntimeException("More than one " + getType().toUpperCase() + " service mapped to " + key);
          }

          RestService srv = new JavaRestService(cls, m, newService, key, getTimeout());
          services.put(srv.key, srv);

          addToContextMap(srv);
       }

       /**
        * Add the given rest service to its context path map.
        *
        * @param    srv
        *           The rest service
        */
       private void addToContextMap(RestService srv)
       {
          RestServiceKey key = (RestServiceKey) srv.key;
          Map<ServiceKey, RestService> contextServices = restServices.get(key.context);
          if (contextServices == null)
          {
             contextServices = new HashMap<>();
             restServices.put(key.context, contextServices);
          }
          contextServices.put(key, srv);
       }

       /**
        * Build the REST service parameter associated with a Java-style defined service.
        *
        * @param    orig
        *           The original signature.
        * @param    type
        *           The parameter's type, as defined at the Java method.
        * @param    name
        *           The parameter's name, as defined at the Java method.
        *
        * @return   See above.
        */
       private LegacyServiceParameter buildJavaParameter(LegacyServiceParameter orig, Type type, String name)
       {
          return new LegacyServiceParameter()
          {
             @Override
             public Class<? extends Annotation> annotationType()
             {
                return LegacyServiceParameter.class;
             }

             @Override
             public String type()
             {
                // 'getTypeName' will return the generic/parametrized type, as in:
                // some.pkg.Foo<SomeType1, SomeType2, ...>
                return type.getTypeName();
             }

             @Override
             public String target()
             {
                return orig.target();
             }

             @Override
             public String source()
             {
                return orig.source();
             }

             @Override
             public boolean returnValue()
             {
                return orig.returnValue();
             }

             @Override
             public boolean output()
             {
                return orig.output();
             }

             @Override
             public int ordinal()
             {
                return orig.ordinal();
             }

             @Override
             public String name()
             {
                return name;
             }

             @Override
             public boolean input()
             {
                return orig.input();
             }

             @Override
             public int extent()
             {
                return orig.extent();
             }

             @Override
             public boolean allowUnknown()
             {
                return orig.allowUnknown();
             }

             @Override
             public String defaultValue()
             {
                return orig.defaultValue();
             }
          };
       }
    }