You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by bi...@apache.org on 2003/08/09 03:59:42 UTC

cvs commit: jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin procrun.dll procrun.exe procrunw.exe tomcat.exe tomcatw.exe

billbarker    2003/08/08 18:59:42

  Modified:    daemon/src/native/nt/procrun procrun.c procrun.h
               daemon/src/native/nt/procrun/bin procrun.dll procrun.exe
                        procrunw.exe tomcat.exe tomcatw.exe
  Log:
  Some changes to cleanup the startup and shutdown.
  
  Fix for Bugs: 22174 and 21279
  
   -- procrun now waits for the shutdown to finish before killing the JVM
   -- procrun strips quotes if running under JNI (so the user doesn't have to)
   -- Added a --Environment configuration parameter for the forking case
   -- Fixed problems where handles were getting closed twice (which XP doesn't like).
  
  Revision  Changes    Path
  1.26      +174 -72   jakarta-commons-sandbox/daemon/src/native/nt/procrun/procrun.c
  
  Index: procrun.c
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/native/nt/procrun/procrun.c,v
  retrieving revision 1.25
  retrieving revision 1.26
  diff -u -r1.25 -r1.26
  --- procrun.c	8 Aug 2003 10:34:31 -0000	1.25
  +++ procrun.c	9 Aug 2003 01:59:42 -0000	1.26
  @@ -102,6 +102,8 @@
   JNI_GETCREATEDJAVAVMS jni_JNI_GetCreatedJavaVMs = NULL;
   
   int report_service_status(DWORD, DWORD, DWORD, process_t *); 
  +int procrun_redirect(char *program, char **envp, procrun_t *env, int starting);
  +
   static int g_proc_stderr_file = 0;
   int g_proc_mode = 0;
   /* The main envronment for services */
  @@ -246,6 +248,18 @@
       return h;
   }
   
  +static BOOL pool_close_handle(pool_t *pool, HANDLE h)
  +{
  +    int i;
  +    EnterCriticalSection(&pool->lock);
  +    for(i=0; i < pool->size; i++) {
  +        if(pool->mp[i].h == h) 
  +            pool->mp[i].h = INVALID_HANDLE_VALUE;
  +    }
  +    LeaveCriticalSection(&pool->lock);
  +    return CloseHandle(h);
  +}
  +
   static void simple_encrypt(int seed, const char *str, unsigned char bytes[256])
   {
       int i;
  @@ -461,6 +475,17 @@
       return i;
   }
   
  +static char *remove_quotes(char * string) {
  +  char *p = string, *q = string;
  +  while (*p) {
  +    if(*p != '\"' && *p != '\'')
  +      *q++ = *p;
  +    ++p;
  +  }
  +  *q = '\0';
  +  return string;
  +}
  +
   /*  Parse command line argument.
    *  First command param starts with //name//value
    */
  @@ -909,7 +934,7 @@
                                                 NULL);
               if (proc->service.infile != INVALID_HANDLE_VALUE) {
                   if (proc->h_stdin[1] != INVALID_HANDLE_VALUE)
  -                    CloseHandle(proc->h_stdin[1]);
  +                    pool_close_handle(proc->pool, proc->h_stdin[1]);
                   proc->h_stdin[1] = proc->service.infile;
                   proc->service.inname = pool_strdup(proc->pool, kval);
               }
  @@ -929,7 +954,7 @@
               if (proc->service.outfile != INVALID_HANDLE_VALUE) {
                   SetFilePointer(proc->service.outfile, 0L, NULL, FILE_END);
                   if (proc->h_stdout[1] != INVALID_HANDLE_VALUE)
  -                    CloseHandle(proc->h_stdout[1]);
  +                    pool_close_handle(proc->pool, proc->h_stdout[1]);
                   proc->h_stdout[1] = proc->service.outfile;
                   proc->service.outname = pool_strdup(proc->pool, kval);
               }
  @@ -949,7 +974,7 @@
               if (proc->service.errfile != INVALID_HANDLE_VALUE) {
                   SetFilePointer(proc->service.errfile, 0L, NULL, FILE_END);
                   if (proc->h_stderr[1] != INVALID_HANDLE_VALUE)
  -                    CloseHandle(proc->h_stderr[1]);
  +                    pool_close_handle(proc->pool, proc->h_stderr[1]);
                   proc->h_stderr[1] = proc->service.errfile;
                   proc->service.errname = pool_strdup(proc->pool, kval);
               }
  @@ -985,6 +1010,17 @@
                                           &ac_winpos.top, &ac_winpos.bottom);
           }
   #endif
  +        if ((err = RegQueryValueEx(key, PROCRUN_PARAMS_ENVIRONMENT, NULL, NULL, 
  +                                   NULL,
  +                                   &klen)) == ERROR_SUCCESS) {
  +            proc->service.environment = (char *)pool_alloc(proc->pool, klen);
  +            if ((err = RegQueryValueEx(key, PROCRUN_PARAMS_ENVIRONMENT, NULL, NULL, 
  +                                   (unsigned char *)proc->service.environment,
  +                                   &klen)) != ERROR_SUCCESS) {
  +                proc->service.environment = NULL;
  +            }
  +        }
  +
           RegCloseKey(key); 
           return 0;
       }
  @@ -1048,6 +1084,9 @@
           DBPRINTF1("java    bin  %s\n", proc->java.jbin);
           if (!proc->java.path || !proc->java.start_method)
               return -1;
  +        else if (proc->java.jbin != NULL) {
  +	        return 0; // If forking, don't bother with the load.
  +        }
           /* Try to load the jvm dll */ 
           em = SetErrorMode(SEM_FAILCRITICALERRORS);
           proc->java.dll = LoadLibraryEx(proc->java.path, NULL, 0); 
  @@ -1134,14 +1173,28 @@
       return (*jvm)->DetachCurrentThread(jvm);
   }
   
  -static int procrun_destroy_jvm(process_t *proc)
  +static int procrun_destroy_jvm(process_t *proc, HANDLE jh)
   {
       JavaVM *jvm = proc->java.jvm;
       int err;
       JNIEnv *env;
   
  -    if (!proc->java.dll || !jvm)
  +    if (!proc->java.dll || !jvm) {
  +        if(proc->java.stop_class != NULL && proc->java.stop_method != NULL && g_env->c->pinfo.dwProcessId) {
  +            process_t tc = *g_env->c, tm = *g_env->m;
  +            procrun_t tproc;
  +            HANDLE threads[2];
  +            tproc.c = &tc;
  +            tproc.m = &tm;
  +            procrun_redirect(proc->service.image,
  +                              proc->envp, &tproc, 0);    
  +            threads[0] = tc.pinfo.hThread;
  +            threads[1] = g_env->c->pinfo.hThread;
  +            WaitForMultipleObjects(2,threads, TRUE, 60000);
  +        }
  +
           return 0;
  +    }
       env = jni_attach(proc);
       if (!env)
           goto cleanup;
  @@ -1160,6 +1213,8 @@
                                        proc->java.stop_bridge,
                                        proc->java.stop_mid,
                                        jargs);
  +        if(jh != NULL)
  +            WaitForSingleObject(jh, 60000);
       }
       else if (!proc->java.jbin) {
           /* Call java.lang.System.exit(0) */
  @@ -1214,11 +1269,11 @@
       
       optn = make_array(proc->java.opts, opts, 30, proc);
       for (i = 0; i < optn; i++)
  -        options[i].optionString = opts[i];
  +        options[i].optionString = remove_quotes(opts[i]);
       cp = (char *)pool_alloc(proc->pool, strlen("-Djava.class.path=") +
                               strlen(proc->service.image) + 1);
       strcpy(cp, "-Djava.class.path=");
  -    strcat(cp, proc->service.image);
  +    strcat(cp, remove_quotes(proc->service.image));
       options[optn++].optionString = cp;
       DBPRINTF1("-Djava.class.path=%s", proc->service.image);
       /* Set the abort and exit hooks */
  @@ -1271,7 +1326,7 @@
                   proc->java.stop_class[i] = '/';
           }
           proc->java.stop_bridge = (*env)->FindClass(env, proc->java.stop_class);
  -        if (!proc->java.start_bridge) {
  +        if (!proc->java.stop_bridge) {
               goto cleanup;
           }    
           proc->java.stop_mid = (*env)->GetStaticMethodID(env, proc->java.stop_bridge,
  @@ -1315,7 +1370,7 @@
       
       Sleep(1000);
       WaitForSingleObject(env->c->pinfo.hThread, INFINITE);
  -    CloseHandle(env->c->pinfo.hThread);
  +    pool_close_handle(env->c->pool, env->c->pinfo.hThread);
       env->c->pinfo.hThread = NULL;
       env->c->pinfo.dwProcessId = 0;
       SetEvent(env->m->events[1]);
  @@ -1443,7 +1498,7 @@
           DBPRINTF0(NULL);
           return -1;
       }
  -    CloseHandle(env->c->h_stdout[0]);
  +    pool_close_handle(env->c->pool, env->c->h_stdout[0]);
       pool_handle(env->c->pool, env->c->h_stdout[3]);
       
       /* redirect stderr */
  @@ -1468,7 +1523,7 @@
           DBPRINTF0(NULL);
           return -1;
       }
  -    CloseHandle(env->c->h_stderr[0]);
  +    pool_close_handle(env->c->pool, env->c->h_stderr[0]);
       pool_handle(env->c->pool, env->c->h_stderr[3]);
   
       /* redirect stdin */
  @@ -1493,7 +1548,7 @@
           return -1;
       }
       
  -    CloseHandle(env->c->h_stdin[1]);
  +    pool_close_handle(env->c->pool, env->c->h_stdin[1]);
       pool_handle(env->c->pool, env->c->h_stdin[3]);
   
       return 0;
  @@ -1519,45 +1574,17 @@
       } 
    
       /* Close the pipe handle so the child process stops reading. */ 
  -    if (!CloseHandle(env->c->h_stdin[3])) 
  +    if (!pool_close_handle(env->c->pool, env->c->h_stdin[3])) 
         return -1; 
       env->c->h_stdin[3] = INVALID_HANDLE_VALUE;
       return 0;
   }
   
  -
  -int procrun_redirect(char *program, char **envp, procrun_t *env)
  -{
  -    STARTUPINFO si;
  -    DWORD  id;
  -
  -    if (!program) {
  -#ifdef PROCRUN_WINAPP
  -        MessageBox(NULL, "Service not found ", env->m->service.name,
  -                         MB_OK | MB_ICONERROR);
  -#else
  -        fprintf(stderr, "Service not found %s\n", env->m->service.name); 
  -#endif
  -        return -1;
  -    }
  -    memset(&si, 0, sizeof(STARTUPINFO));
  -    si.cb = sizeof(si);
  -    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  -    si.wShowWindow = SW_HIDE;
  -
  -    if (procrun_create_pipes(env))
  -        return -1;
  -    si.hStdOutput = env->c->h_stdout[1];
  -    si.hStdError  = env->c->h_stderr[1];
  -    si.hStdInput  = env->c->h_stdin[0]; 
  -    
  -    env->m->envw = make_environment(env->c->env, envp, env->m);
  -    DBPRINTF1("Creating process %s.\n", program);
  -    DBPRINTF1("Creating process %s.\n", env->m->argw);
  -    /* for java.exe merge Arguments and Java options */
  -    if (env->m->java.jbin) {
  +static char * set_command_line(procrun_t *env, char *program, int starting){
           int i, j, len = strlen(env->m->argw) + 8192;
           char *opts[64], *nargw;
  +        char *javaClass = starting ? env->m->java.start_class : env->m->java.stop_class,
  +            *javaParam = starting ? env->m->java.start_param : env->m->java.stop_param;
   
           j = make_array(env->m->java.opts, opts, 60, env->m);
   
  @@ -1565,7 +1592,10 @@
               len += strlen(opts[i]);
          
           nargw = pool_calloc(env->m->pool, len);
  -        strcpy(nargw, env->m->argw);
  +        if(starting)
  +            strcpy(nargw, env->m->argw);
  +        else
  +            strcpy(nargw, "java");
           strcat(nargw, " ");
           for (i = 0; i < j; i++) {
               strcat(nargw, opts[i]);
  @@ -1580,16 +1610,54 @@
           else
               strcat(nargw, program);
           strcat(nargw, " ");
  -        strcat(nargw, env->m->java.start_class);
  -        if (env->m->java.start_param) {
  +        strcat(nargw, javaClass);
  +        if (javaParam) {
               strcat(nargw, " ");
  -            strcat(nargw, env->m->java.start_param);
  +            strcat(nargw, javaParam);
           }
           env->m->argw = nargw;
           program = env->m->java.jbin;
  +	return program;
  +}
  +
  +int procrun_redirect(char *program, char **envp, procrun_t *env, int starting)
  +{
  +    STARTUPINFO si;
  +    DWORD  id;
  +
  +    if (!program) {
  +#ifdef PROCRUN_WINAPP
  +        MessageBox(NULL, "Service not found ", env->m->service.name,
  +                         MB_OK | MB_ICONERROR);
  +#else
  +        fprintf(stderr, "Service not found %s\n", env->m->service.name); 
  +#endif
  +        return -1;
  +    }
  +    memset(&si, 0, sizeof(STARTUPINFO));
  +    si.cb = sizeof(si);
  +    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  +    si.wShowWindow = SW_HIDE;
  +
  +    if(starting) {
  +        if (procrun_create_pipes(env))
  +            return -1;
  +        si.hStdOutput = env->c->h_stdout[1];
  +        si.hStdError  = env->c->h_stderr[1];
  +        si.hStdInput  = env->c->h_stdin[0]; 
  +    }
  +    else
  +        si.dwFlags = STARTF_USESHOWWINDOW;
  +    
  +    env->m->envw = make_environment(env->c->env, envp, env->m);
  +    DBPRINTF1("Creating process %s.\n", program);
  +    DBPRINTF1("Creating process %s.\n", env->m->argw);
  +    /* for java.exe merge Arguments and Java options */
  +    if (env->m->java.jbin) {
  +      program = set_command_line(env, program, starting);
       }
       DBPRINTF2("RUN [%s] %s\n", program, env->m->argw);
  -    if (env->m->service.account && env->m->service.password) {
  +    if (env->m->service.account && env->m->service.password && starting) {
           HANDLE user, token;
           DBPRINTF2("RUNASUSER %s@%s\n", env->m->service.account, env->m->service.password);
           if (!LogonUser(env->m->service.account, 
  @@ -1651,22 +1719,26 @@
               return -1;
           }
       }
  -    pool_handle(env->c->pool, env->c->pinfo.hThread);
  -    pool_handle(env->c->pool, env->c->pinfo.hProcess);
  -
  -    SetStdHandle(STD_OUTPUT_HANDLE, env->m->h_stdout[0]);
  -    SetStdHandle(STD_ERROR_HANDLE, env->m->h_stderr[0]);
  -    SetStdHandle(STD_INPUT_HANDLE, env->m->h_stdin[0]);  
  -    CloseHandle(env->c->h_stdout[1]);
  -    CloseHandle(env->c->h_stderr[1]);
  -    CloseHandle(env->c->h_stdin[0]); 
  -
  -    CloseHandle(CreateThread(NULL, 0, stdout_thread, env, 0, &id));     
  -    CloseHandle(CreateThread(NULL, 0, stderr_thread, env, 0, &id));     
  -    ResumeThread(env->c->pinfo.hThread);    
  -    CloseHandle(CreateThread(NULL, 0, wait_thread, env, 0, &id));     
  +    if(starting) {
  +        pool_handle(env->c->pool, env->c->pinfo.hThread);
  +        pool_handle(env->c->pool, env->c->pinfo.hProcess);
  +
  +        SetStdHandle(STD_OUTPUT_HANDLE, env->m->h_stdout[0]);
  +        SetStdHandle(STD_ERROR_HANDLE, env->m->h_stderr[0]);
  +        SetStdHandle(STD_INPUT_HANDLE, env->m->h_stdin[0]);  
  +        pool_close_handle(env->c->pool, env->c->h_stdout[1]);
  +        pool_close_handle(env->c->pool, env->c->h_stderr[1]);
  +        pool_close_handle(env->c->pool, env->c->h_stdin[0]); 
  +
  +        CloseHandle(CreateThread(NULL, 0, stdout_thread, env, 0, &id));     
  +        CloseHandle(CreateThread(NULL, 0, stderr_thread, env, 0, &id));     
  +        ResumeThread(env->c->pinfo.hThread);    
  +        CloseHandle(CreateThread(NULL, 0, wait_thread, env, 0, &id));     
   
  -    procrun_write_stdin(env);
  +        procrun_write_stdin(env);
  +    }
  +    else
  +        ResumeThread(env->c->pinfo.hThread);        
       return 0;
   }
   
  @@ -1801,6 +1873,9 @@
                   strcat(path, " " PROC_ARG_RUN_SERVICE);
                   strcat(path, proc->service.name);
               }
  +            else if (STRNI_COMPARE(argp, PROCRUN_PARAMS_ENVIRONMENT)) {
  +              proc->service.environment = pool_strdup(proc->pool, argv[++i]);
  +            }
               else {
                   DBPRINTF1("Unrecognized option %s\n", argv[i]);
                   break;
  @@ -1903,7 +1978,19 @@
               set_service_param(proc, PROCRUN_PARAMS_PASSWORD,
                                 b, 256, 2);
           }
  +        
  +    }
  +    if (proc->service.environment) {
  +        int l = strlen(proc->service.environment);
  +        for(i=0; i < l; i++) {
  +            if(proc->service.environment[i] == '#')
  +                proc->service.environment[i] = '\0';
  +        }
  +        set_service_param(proc, PROCRUN_PARAMS_ENVIRONMENT,
  +                        proc->service.environment, 1+2, 0);
       }
  +
  +
       if (proc->service.startup != SERVICE_NO_CHANGE)
           set_service_param(proc, PROCRUN_PARAMS_STARTUP,
               proc->service.startup == SERVICE_AUTO_START ? "auto" : "manual",
  @@ -1920,7 +2007,6 @@
                             proc->java.opts, l + 2, 0);
       }
   }
  -
   static int procrun_install_service(process_t *proc, int argc, char **argv)
   {
       SC_HANDLE service;
  @@ -2136,6 +2222,8 @@
                   proc->service.account = pool_strdup(proc->pool, argv[++i]);
               else if (STRNI_COMPARE(argp, PROCRUN_PARAMS_PASSWORD))
                   proc->service.password = pool_strdup(proc->pool, argv[++i]);
  +            else if (STRNI_COMPARE(argp, PROCRUN_PARAMS_ENVIRONMENT))
  +                proc->service.environment = pool_strdup(proc->pool, argv[++i]);
               else
                   break;
           }
  @@ -2193,6 +2281,7 @@
       return 0;
   }
   
  +
   static int procrun_delete_service(process_t *proc)
   {
       SC_HANDLE service;
  @@ -2521,6 +2610,7 @@
   int service_main(int argc, char **argv)
   {
       DWORD fired = 0;
  +    HANDLE jh;
       int   rv = -1;
       
       
  @@ -2546,7 +2636,6 @@
       if (g_env->m->java.dll) {
           if (g_env->m->java.jbin == NULL) {
               DWORD id;
  -            HANDLE jh;
               jh = CreateThread(NULL, 0, java_thread, g_env, 0, &id);
               pool_handle(g_env->m->pool, jh);
               rv = 0;
  @@ -2554,13 +2643,13 @@
           else {
               if ((rv = procrun_init_jvm(g_env->m)) == 0) {
                   rv = procrun_redirect(g_env->m->service.image,
  -                                      g_env->m->envp, g_env);    
  +                                      g_env->m->envp, g_env, 1);    
               }
           }
       } 
       else {
           rv = procrun_redirect(g_env->m->service.image,
  -                              g_env->m->envp, g_env);    
  +                              g_env->m->envp, g_env, 1);    
       }
       if (rv == 0) {
           report_service_status(SERVICE_RUNNING, NO_ERROR, 0,
  @@ -2583,7 +2672,7 @@
       DBPRINTF1("Stoping Service %s\n", g_env->m->service.name);
       report_service_status(SERVICE_STOP_PENDING, NO_ERROR, 3000,
                             g_env->m);
  -    procrun_destroy_jvm(g_env->m);
  +    procrun_destroy_jvm(g_env->m, jh);
   
       inject_exitprocess(&g_env->c->pinfo);
       report_service_status(SERVICE_STOPPED, 0, 0,
  @@ -2649,6 +2738,19 @@
       /* Check if we have a JVM */
       if (procrun_load_jvm(env->m, mode) < 0)
           goto cleanup;
  +    if(env->m->service.environment) {
  +        char *nenv = env->m->service.environment;
  +        while(*nenv) {
  +            char *cenv = pool_strdup(env->c->pool, nenv);
  +            char *equals = strchr(cenv, '=');
  +            if(equals != NULL) {
  +                char *value = nenv + (equals-cenv)+1;
  +                *++equals = '\0';
  +                procrun_addenv(cenv, value, 0, env->c);
  +            }
  +            nenv += strlen(nenv)+1;
  +        }
  +    }
       sprintf(event, "PROC_SHUTDOWN_EVENT%d", GetCurrentProcessId());
       env->m->events[0] = CreateEvent(NULL, TRUE, FALSE, event);
       sprintf(event, "PROC_EXITWAIT_EVENT%d", GetCurrentProcessId());
  @@ -2746,7 +2848,7 @@
               }
               env->m->service.name = argv[0];
               env->m->service.description = argv[2];
  -            rv = procrun_redirect(argv[2], envp, env);
  +            rv = procrun_redirect(argv[2], envp, env, 1);
               break;
   
   #endif
  @@ -2754,7 +2856,7 @@
               /* Run the process 
                * We can be master or called by env.
                */
  -            rv = procrun_redirect(argv[1], envp, env);
  +            rv = procrun_redirect(argv[1], envp, env, 1);
               break;
       }
       
  @@ -2776,7 +2878,7 @@
       if (ac_main_hwnd)
           ac_show_try_icon(ac_main_hwnd, NIM_DELETE, NULL);
   #endif
  -    procrun_destroy_jvm(env->m);
  +    procrun_destroy_jvm(env->m, NULL);
       inject_exitprocess(&env->c->pinfo);
       i = pool_destroy(env->m->pool);
       i = pool_destroy(env->c->pool);
  
  
  
  1.21      +2 -0      jakarta-commons-sandbox/daemon/src/native/nt/procrun/procrun.h
  
  Index: procrun.h
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/native/nt/procrun/procrun.h,v
  retrieving revision 1.20
  retrieving revision 1.21
  diff -u -r1.20 -r1.21
  --- procrun.h	8 Aug 2003 10:57:13 -0000	1.20
  +++ procrun.h	9 Aug 2003 01:59:42 -0000	1.21
  @@ -198,6 +198,7 @@
   #define PROCRUN_PARAMS_ACCOUNT      "User"
   #define PROCRUN_PARAMS_PASSWORD     "Password"
   #define PROCRUN_PARAMS_INSTALL      "Install"
  +#define PROCRUN_PARAMS_ENVIRONMENT  "Environment"
   /* Console Window position and color */
   #define PROCRUN_PARAMS_WINPOS       "WindowPosition"
   #define PROCRUN_PARAMS_WINCLR       "WindowColor"
  @@ -269,6 +270,7 @@
           char                  *image;
           char                  *account;
           char                  *password;
  +        char                  *environment;
           HANDLE                infile;
           HANDLE                outfile;
           HANDLE                errfile;
  
  
  
  1.12      +34 -27    jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin/procrun.dll
  
  	<<Binary file>>
  
  
  1.14      +32 -22    jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin/procrun.exe
  
  	<<Binary file>>
  
  
  1.28      +38 -36    jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin/procrunw.exe
  
  	<<Binary file>>
  
  
  1.10      +32 -22    jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin/tomcat.exe
  
  	<<Binary file>>
  
  
  1.23      +38 -36    jakarta-commons-sandbox/daemon/src/native/nt/procrun/bin/tomcatw.exe
  
  	<<Binary file>>
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org