Project

General

Profile

StandardServer.java

Galya B, 06/23/2023 02:54 AM

Download (89.9 KB)

 
1
/*
2
** Module   : StandardServer.java
3
** Abstract : standard server application
4
**
5
** Copyright (c) 2005-2023, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- --JPRM-- ---------------------------------Description-------------------------------------
8
** 001 NVS 20050831   @22451 Created the initial version. 
9
** 002 NVS 20050915   @22735 Alternative path /server/default/exports is
10
**                           tried if no /server/serverID/exports exists.
11
**                           Standard entry point processing added, based
12
**                           on directory entries under /server/serverID/
13
**                           entry/accountID.
14
** 003 NVS 20051221   @23733 Saves the reference to the queue in the session
15
**                           context so it can be accessed at any time.
16
** 004 GES 20060110   @23850 Implemented proper transaction manager
17
**                           infrastructure and init such that converted
18
**                           P2J applications can be run with this
19
**                           class as their launch point.
20
** 005 ECF 20060114   @23904 Moved instantiation of target class to the
21
**                           invoke() method. This is necessary to include
22
**                           constructors/initializers within the first
23
**                           TransactionManager scope created. Modified
24
**                           various catch statements to not discard stack
25
**                           traces of caught exceptions.
26
** 006 GES 20060130   @24143 Added quit state flag processing in invoke.
27
** 007 GES 20060202   @24229 Added new flag in pushScope() signature.
28
** 008 GES 20060203   @24244 Changed TransactionManager method name
29
**                           makeBackup() to blockSetup().
30
** 009 GES 20060210   @24528 Shifted to a simpler approach for server
31
**                           exports. First pass (more is coming later).
32
** 010 NVS 20060226   @24750 Made portions of invoke() reusable in Utils.
33
**                           Calling reusable invoke() method from here.
34
** 011 GES 20060305   @24891 Cleaned up logging.
35
** 012 NVS 20060306   @24901 Added exports registration for ErrorManager.
36
** 013 GES 20060314   @25062 Fixed exception processing to properly retry on STOP.
37
** 014 NVS 20060417   @25578 Added support for the password aging. Before
38
**                           the configured application entry point is
39
**                           taken, the user account is checked for the
40
**                           aged password, and, if so, the password
41
**                           change method is called..
42
** 015 GES 20060608   @27054 Added logging of any abnormal end before it
43
**                           is re-thrown.  ConditionExceptions and
44
**                           RetryUnwindExceptions are not logged as these
45
**                           are "normal" transaction manager flow of
46
**                           control interactions.
47
** 016 NVS 20060614   @27196 Fixed a problem with NET package exports of
48
**                           SecurityManager class.
49
**                           Added a call to RemoteObject to do deferred
50
**                           registrations.
51
** 017 GES 20060804   @28432 Honor new iterate() processing which only
52
**                           happens at the top of the block body.
53
** 018 NVS 20060617   @30461 Implemented stop_disposition configuration
54
**                           option reading and honoring when the business
55
**                           logic encounters STOP condition.
56
** 019 GES 20061212   @31666 Force "quit" processing in any fatal abend
57
**                           (something other than one of the "expected"
58
**                           4GL conditions).  This avoids a call to
59
**                           LogicalTerminal.pauseBeforeEnd() which will
60
**                           fail in any case where we are exiting because
61
**                           the client was forcefully disconnected.
62
** 020 GES 20061219   @31718 Removed dead code.
63
** 021 GES 20070111   @31782 Reworked notifiable interface as needed.
64
** 022 NVS 20070126   @32004 Application entry point is now the subject to
65
**                           the standardized search in the directory.
66
**                           The node is named "p2j-entry" to separate it
67
**                           from the server level entry point.
68
** 023 NVS 20070221   @32381 Administration Server exports are registered.
69
** 024 EVL 20070609   @34005 Adding explicit import of the class
70
**                           com.goldencode.p2j.net.Queue to eliminate
71
**                           conflict with the same class from java.util
72
**                           package to be able to compile for Java 6
73
** 025 GES 20071011   @35424 Eliminated compile warning.
74
** 026 ECF 20071112   @35892 Refactored to leverage net package changes.
75
**                           Replaced initialize() with bootstrap(). All
76
**                           non-network function that was previously in
77
**                           ServerBootstrap is now in this class, while
78
**                           the network-specific function is now handled
79
**                           by the net package's SessionManager.
80
** 027 GES 20071214   @36410 Added code to translate unexpected exceptions
81
**                           (but not errors) into a stop condition. This
82
**                           will avoid the silent drop-out to the command
83
**                           line unless the stop disposition is also
84
**                           configured to silently exit (disposition 2).
85
** 028 GES 20080409   @37912 Export remote directory access when the network protocol is ready.
86
** 029 NVS 20090312   @41522 Added initialization of the AdminServerImpl class.
87
** 030 SIY 20090507   @42111 Added support for context hooks.
88
** 031 SIY 20090511   @42142 Added support for server hooks.
89
** 032 SIY 20090512   @42144 Reworked server hooks to support more than one server hook.
90
** 033 SIY 20090515   @42188 All services now are started as server hooks and hardcoded.
91
** 034 NVS 20090528   @42482 Custom account extension plugin is loaded at startup, if defined
92
**                           in the directory
93
** 035 GES 20090722   @43331 Handle chained errors as errors not exceptions.
94
** 036 GES 20090723   @43359 Avoid logging STOP conditions.
95
** 037 NVS 20090817   @43684 Server hook load failures are logged but are not critical for the
96
**                           server startup.
97
** 038 NVS 20090818   @43690 Excluded the exception stack trace from the error message in #037.
98
** 039 NVS 20090821   @43711 Admin Enabled flag is now searched correctly scoped to the per 
99
**                           server scope.
100
** 040 NVS 20090826   @43775 Passing server name to the LogHelper.initialize().
101
** 041 ECF 20090827   @43787 Fixed logging to file. SecureFileHandler is now used when
102
**                           initializing the LogHelper, and is closed when the server is 
103
**                           finished shutting down.
104
** 042 CA  20090901   @43809 On bootstrap, SessionManager will be set a field which contains the
105
**                           InterruptHandler implementation.
106
** 043 CA  20090917   @43927 Fix abnormal connection end. In invoke(), any SilentUnwindExceptions 
107
**                           will stop the Conversation thread.
108
** 044 NVS 20090918   @43950 Reworked invoke() method to take an instance of Isolatable interface
109
**                           to allow the reuse of invoke for admin server purposes where some
110
**                           methods have to run in a transaction manager environment to be able
111
**                           to access the database properly, including modifications. Reworked 
112
**                           standardEntry() method to comply with the new requirements; added
113
**                           the inner class MainInvoker that encapsulates the client entry
114
**                           specific logic.
115
** 045 NVS 20090928   @44048 Server startup is protected against non-existent directory.
116
** 046 LMR 20101210          Edited bootstrap() and registerDefaultServices() to initialize the
117
**                           AdminGate service only if net/connection/secure is set to true in 
118
**                           the bootstrap config (if not found it defaults to false).
119
** 047 GES 20111004          Reworked the web server startup to more generically support multiple
120
**                           applets (not just admin alone).
121
** 048 GES 20111025          Signature changes in calling RemoteObject to export methods. Added
122
**                           waitUntilReady().
123
** 049 CA  20111130          Use the system classloader to load hooks, as Class.forName caches
124
**                           the loaded class in some native code from which it can never be
125
**                           removed. Before initializing hooks and exports, create class loaders
126
**                           for all the jars defined in the directory.
127
** 050 CA  20130529          Added appserver support.
128
** 051 CA  20130705          Register the appserver launcher.
129
** 052 SVL 20130624          Set runtime Configuration to be used.
130
** 053 HC  20130902          For #2164 added warning log when server persistence is inactive.
131
** 054 CA  20131004          Expose client-side parameter to server-side application code.
132
** 055 CA  20131023          At runtime, the in-memory registry plugin must be used.
133
** 056 MAG 20131101          Upgrade to jetty 9.1 embedded server.
134
** 057 MAG 20131114          Add Handler for Chui Web client.
135
** 058 OM  20131210          Made DatabaseTriggerManager Scopable context-local instantiation.
136
** 059 EVL 20131115          Adding UnstoppableExitException handling for unrecoverable errors in
137
**                           batch mode. The serve side of the ErrorManages is also initialized
138
**                           here.
139
** 060 MAG 20131223          Get Chui Web client target root from directory. Initialize temporary
140
**                           accounts pool.
141
** 061 MAG 20140128          Change the main return type from boolean to Result. For web 
142
**                           clients provide temporary account credentials.
143
** 062 MAG 20140131          Reverse changes at 061 (2014-01-28).
144
** 063 CA  20140206          Added scheduler support.
145
** 064 MAG 20140217          Added terminate method to web server hooker.
146
** 065 EVL 20140327          Adding the support to get the server properties to client. 
147
** 066 ECF 20140404          Initialize conversion pool during bootstrap.
148
** 067 ECF 20140430          Removed stack trace from conversion pool initialization warning.
149
**                           Enhanced warning message and added debug-level logging to provide
150
**                           more information and stack trace.
151
** 068 MAG 20140707          Implements remote launcher (broker). Exports broker API.
152
** 069 GES 20141229          Added support for user-specified entry points.
153
** 070 GES 20150310          Made web client support more generic (it isn't chui-specific now).
154
** 071 CA  20150622          Context-local client parameters (localParams field) must not be reset.
155
**                           Added database statistics collection (ECF).
156
** 072 CA  20160205          Added ServerKeyStore.initialize() at the server startup hooks.
157
** 073 IAS 20160329          Provide client-side parameters in a separate call
158
** 074 GES 20160121          Added WebClientLauncher service registration.
159
** 075 OM  20160422          Added project token implementation for multiple project support.
160
** 076 OM  20160527          SourceNameMapper.convertName() changed to convertNameToClass().
161
** 077 OM  20160927          Activated BatchMode when expressly requested by client.
162
** 078 HC  20170118          FWD version is printed to the log output on server start.
163
** 079 CA  20170228          Added PUBLISH/SUBSCRIBE/UNSUBSCRIBE extensions for global support and
164
**                           for external applications.
165
** 080 SBI 20170628          Added sessions listener to manage web client sessions.
166
** 081 HC  20170612          Changes related to implementation of new GWT-based Admin client.
167
** 082 ECF 20180615          Read list of jars containing application resources from the
168
**                           directory during bootstrap, so resource searches can be limited to
169
**                           these jars.
170
** 083 CA  20180724          Allow the embedded web app server to run from within FWD.
171
** 084 CA  20190611          Added support for legacy services (REST).
172
** 085 CA  20190703          Added support for WebHandler services.
173
** 086 ECF 20190521          Register a controller at server startup for method execution tracing.
174
** 087 CA  20190710          The legacy services need to be registered after their handlers have
175
**                           been initialized.
176
** 088 GES 20200213          Bypass termination of embedded mode, REST services and the web
177
**                           handler when if they were never initialized.
178
** 089 CA  20200514          Added support for SOAP web services.
179
**     CA  20200527          Allow the project token to be overridden via a client:project:token setting.
180
** 090 CA  20200915          Upgraded to Jetty 9.4.22.
181
** 091 CA  20200930          Force ANTLR to use Class.forName instead of loadClass.
182
** 092 SBI 20210413          Added web content handlers to public static content.
183
**     CA  20210511          The client's entry point (be it from directory's 'p2j-entry' or the client's
184
**                           startup-procedure configuration) will be executed as a "RUN external-program"
185
**                           statement, if the target is an external program which can be resolved.
186
**     CA  20210512          Refactored CA/20210511 so that the legacy RUN emulation and the old 'execute'
187
**                           method are separated.
188
**     IAS 20210520          Added support for session-based database auto-connect
189
**     HC  20210725          Added support for v6colon.
190
**     GES 20210827          Added driver-level diagnostics information logged at server startup.
191
**     SBI 20210923          Added FontTable initialization.
192
**     OM  20190620          Added -profile command line option for specifying the configuration profile.
193
**     CA  20210928          Set the temp directory for the FWD admin web app.
194
**     CA  20211114          The InMemoryRegistryPlugin instance at the AstManager must be context-local, for
195
**                           runtime conversion to work in concurrent mode.
196
**     CA  20220405          Added authentication and authorization for web requests.  When this is enabled, 
197
**                           the target API call will be executed under the authenticated FWD context, and not 
198
**                           the agent's context.
199
**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
200
**                           registration is now specific to each type of scopeable.  For each case, the block
201
**                           will be registered for scope support (for that particular scopeable) only when
202
**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
203
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
204
**                           processing all the scopeables for each and every block.
205
**     CA  20220906          Moved the interactive client cleaner to LogicalTerminal.registerCleaner().
206
**     TJD 20220504          Migration to Java 11 minor changes
207
**     SBI 20221215          Changed log message for loadStartup to specify the failure cause.
208
**     CA  20220501          Each profile has the same structure as the main 'global' config.  Allow multiple
209
**                           profiles to be ran at once, with the conversion switching the state between 
210
**                           profiles, when a resource (like a file or namespace) is being processed.  Only
211
**                           front phase is supported at this time.
212
**     CA  20220520          Initialize SourceNameMapper at server startup.
213
** 093 VVT 20230318          UnitTestEngine static network server registration added. See #6237.
214
** 094 GBB 20230412          Initialize LegacyLogManager.
215
** 095 GBB 20230608          Added a flag to LegacyLogManagerConfigs for server-side filesystem in use.
216
*/
217

    
218
/*
219
** This program is free software: you can redistribute it and/or modify
220
** it under the terms of the GNU Affero General Public License as
221
** published by the Free Software Foundation, either version 3 of the
222
** License, or (at your option) any later version.
223
**
224
** This program is distributed in the hope that it will be useful,
225
** but WITHOUT ANY WARRANTY; without even the implied warranty of
226
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
227
** GNU Affero General Public License for more details.
228
**
229
** You may find a copy of the GNU Affero GPL version 3 at the following
230
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
231
** 
232
** Additional terms under GNU Affero GPL version 3 section 7:
233
** 
234
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
235
**   terms apply to the works covered under the License.  These additional terms
236
**   are non-permissive additional terms allowed under Section 7 of the GNU
237
**   Affero GPL version 3 and may not be removed by you.
238
** 
239
**   0. Attribution Requirement.
240
** 
241
**     You must preserve all legal notices or author attributions in the covered
242
**     work or Appropriate Legal Notices displayed by works containing the covered
243
**     work.  You may not remove from the covered work any author or developer
244
**     credit already included within the covered work.
245
** 
246
**   1. No License To Use Trademarks.
247
** 
248
**     This license does not grant any license or rights to use the trademarks
249
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
250
**     of Golden Code Development Corporation. You are not authorized to use the
251
**     name Golden Code, FWD, or the names of any author or contributor, for
252
**     publicity purposes without written authorization.
253
** 
254
**   2. No Misrepresentation of Affiliation.
255
** 
256
**     You may not represent yourself as Golden Code Development Corporation or FWD.
257
** 
258
**     You may not represent yourself for publicity purposes as associated with
259
**     Golden Code Development Corporation, FWD, or any author or contributor to
260
**     the covered work, without written authorization.
261
** 
262
**   3. No Misrepresentation of Source or Origin.
263
** 
264
**     You may not represent the covered work as solely your work.  All modified
265
**     versions of the covered work must be marked in a reasonable way to make it
266
**     clear that the modified work is not originating from Golden Code Development
267
**     Corporation or FWD.  All modified versions must contain the notices of
268
**     attribution required in this license.
269
*/
270

    
271
package com.goldencode.p2j.main;
272

    
273
import java.io.*;
274
import java.lang.reflect.*;
275
import java.text.*;
276
import java.util.*;
277
import java.util.concurrent.*;
278
import java.util.function.*;
279
import java.util.logging.*;
280
import java.util.logging.Formatter;
281

    
282
import javax.servlet.*;
283

    
284
import org.eclipse.jetty.server.handler.*;
285
import org.eclipse.jetty.util.resource.Resource;
286
import org.eclipse.jetty.webapp.*;
287

    
288
import com.goldencode.ast.*;
289
import com.goldencode.p2j.*;
290
import com.goldencode.p2j.admin.*;
291
import com.goldencode.p2j.admin.server.*;
292
import com.goldencode.p2j.aspects.ltw.*;
293
import com.goldencode.p2j.cfg.*;
294
import com.goldencode.p2j.cfg.Configuration;
295
import com.goldencode.p2j.classloader.*;
296
import com.goldencode.p2j.directory.*;
297
import com.goldencode.p2j.net.*;
298
import com.goldencode.p2j.persist.*;
299
import com.goldencode.p2j.persist.trigger.*;
300
import com.goldencode.p2j.rest.*;
301
import com.goldencode.p2j.scheduler.*;
302
import com.goldencode.p2j.security.*;
303
import com.goldencode.p2j.security.SecurityManager;
304
import com.goldencode.p2j.soap.*;
305
import com.goldencode.p2j.testengine.*;
306
import com.goldencode.p2j.ui.*;
307
import com.goldencode.p2j.util.*;
308
import com.goldencode.p2j.util.logging.*;
309
import com.goldencode.p2j.util.ErrorManager;
310

    
311
/**
312
 * Standard server application which is driven by the P2J directory contents.
313
 * <p>
314
 * In particular, exported entry points are read from directory objects
315
 * /server/serverID/exports/groupID/methodID where methodID is a named string
316
 * object. The name of this object is the well known exported method name.
317
 * The "value" attribute is a string encoding fully qualified class name,
318
 * method name and, possibly, the signature as in class.method(signature)
319
 * <p>
320
 * The directory also encodes the standard entry point of the P2J converted
321
 * application which is to be invoked.  This class provides the mechanism
322
 * to look up this entry point and invoke it (see {@link #standardEntry} and
323
 * {@link #invoke}).
324
 */
325
public class StandardServer
326
{
327
   /** Logger (this is JVM-wide rather than being context-local). */
328
   private static final CentralLogger LOG = 
329
      CentralLogger.get(StandardServer.class.getName());
330
   
331
   /** File name extension for jar files */
332
   private static final String JAR_EXT = ".jar";
333
   
334
   /** Name of P2J jar */
335
   private static final String P2J_JAR = "p2j.jar";
336
   
337
   /** Context-local client-side parameters exposed to the server. */
338
   private static final ContextLocal<ClientParameters> localParams = 
339
      new ContextLocal<ClientParameters>()
340
   {
341
      protected boolean isResetAllowed()
342
      {
343
         return false;
344
      }
345
   };
346
   
347
   /** Array of paths to jars in the classpath which hold application resources */
348
   private static List<String> resourceJarPaths;
349
   
350
   /** Lock object for shutdown synchronization. */
351
   private static final Object shutdownLock = new Object();
352
   
353
   /** Flag which is set to <code>true</code> when shutdown is done. */
354
   private static boolean shutdownDone = false;
355
   
356
   /** Server hooks. */
357
   private static final ArrayList<InitTermListener> serverHooks = new ArrayList<>(); 
358
   
359
   /** Map that keeps instantiated classes by name. */
360
   private final Map<String, Object> inst = new HashMap<>();
361
   
362
   /** Signals when the non-network portions of the server are ready. */
363
   private final CountDownLatch ready = new CountDownLatch(1);
364
   
365
   /** Admin extension. */
366
   private AdminServerExtension adminServerExtension;
367
   
368
   /**
369
    * Set the client parameters.
370
    * 
371
    * @param   params
372
    *          A container with any client-related parameters.
373
    *           
374
    */
375
   public static void setClientParams(ClientParameters params)
376
   {
377
      localParams.set(params);
378
   }
379

    
380
   /**
381
    * The method loads client startup parameters from the directory.
382
    *
383
    * @return  Object holding the client startup parameters.
384
    */
385
   public static StartupParameters loadStartupParameters()
386
   {
387
      StartupParametersLoader loader = new StartupParametersLoader();
388
      return loader.load(DirectoryService.getInstance());
389
   }
390
   
391
   /**
392
    * Set the project token to the server-side.
393
    * 
394
    * @param   token
395
    *          the token that identifies the project.
396
    *          
397
    * @see   Utils#setProjectToken(String)
398
    */
399
   public static void setProjectToken(String token)
400
   {
401
      // access the directory service
402
      DirectoryService ds = DirectoryService.getInstance();
403
      
404
      if (!ds.bind())
405
      {
406
         throw new RuntimeException("directory bind failed");
407
      }
408
      
409
      // set the token before we do directory lookup
410
      if (token == null || token.isEmpty())
411
      {
412
         // client's client:project:token overrides any directory setting.
413
         token = Utils.getDirectoryNodeString(ds, "project_token", null);
414
      }
415

    
416
      if (token != null && !token.isEmpty())
417
      {
418
         Utils.setProjectToken(token);
419
         // some settings could have been already set at a previous moment when project_token was
420
         // not available yet
421
         EnvironmentOps.setSearchPath(Utils.getDirectoryNodeString(ds, "searchpath", "."), false);
422
         // TODO: update other settings
423
      }
424
      
425
      ds.unbind();
426
   }
427
   
428
   /**
429
    * Serves as a standard transaction entry point to be called from the client which reroutes control to a 
430
    * specific entry point which is defined in the directory.
431
    * <p>
432
    * The entry point definition is the subject to a standardized directory lookup, which allows server or 
433
    * account specific definitions as well as server-wide or system-wide defaults.
434
    * <p>
435
    * p2j-entry can refer to a public method of the class. It can be either a static or an instance method 
436
    * taking no arguments. The class is instantiated before calling the named method. The returned object, if
437
    * any, is discarded.
438
    * <p>
439
    * If the resolved class name is mapped to a legacy external program, and the method's name is 
440
    * <code>execute</code>, the {@link LegacyInvoker} will be used to execute the program.  Otherwise, the
441
    * {@link MainInvoker} will invoke the method, which will bypass the normal state processing for a legacy 
442
    * external program.
443
    * <p>
444
    * The {@link #invoke} method is the worker that actually handles the method's invocation using reflection.
445
    * <p>
446
    * If directory contains definition for context hook, then it is instantiated. Hook definition directory 
447
    * entry is searched using the same rules as application entry point (refer to
448
    * {@link Utils#findDirectoryNodePath(DirectoryService, String, String, boolean)} for more details), 
449
    * appropriate variable has name <b>context-hook</b>.
450
    * Context hook class must implement {@link InitTermListener} interface.
451
    * 
452
    * @return  <code>true</code> if re-logon should be performed.  
453
    */
454
   public static boolean standardEntry()
455
   {
456
      SecurityManager sm = SecurityManager.getInstance();
457
      String userId = sm.getUserId();
458

    
459
      ClientParameters params = localParams.get();
460
      boolean isServerSideFileSystem = OSResourceManager.getInstance().isServerSideFileSystem();
461
      LegacyLogManagerConfigs legacyLogManagerConfigs = new LegacyLogManagerConfigs(params.clientLog,
462
                                                                                    params.loggingLevel,
463
                                                                                    params.logEntryTypes,
464
                                                                                    params.logThreshold,
465
                                                                                    params.numLogFiles,
466
                                                                                    params.pid,
467
                                                                                    params.inp,
468
                                                                                    params.osUserName,
469
                                                                                    userId,
470
                                                                                    isServerSideFileSystem);
471
      
472
      // this is an app server and needs its own management
473
      if (AppServerManager.startAppServer(legacyLogManagerConfigs))
474
      {
475
         return false;
476
      }
477
      
478
      LegacyLogManager logManager = LegacyLogOps.logMgr();
479
      logManager.initialize(legacyLogManagerConfigs);
480
      
481
      if (params.batchMode > 0)
482
      {
483
         // run this connection in batch mode (as requested from client command-line)
484
         EnvironmentOps.setBatchMode(true);
485
         // LogicalTerminal.activateBatchMode(true); -- should already be set ?
486
      }
487
      
488
      // access the directory service
489
      DirectoryService ds = DirectoryService.getInstance();
490
      
491
      if (!ds.bind())
492
      {
493
         throw new RuntimeException("directory bind failed");
494
      }
495
      
496
      // get the entry point method definition and any configured session hooks; these values
497
      // need no security check since the directory is an inherently trusted source; anyone that
498
      // can control the directory can control everything about the server so there is no
499
      // security check that can make this any better here
500
      String entryPath = Utils.findDirectoryNodePath(ds, "p2j-entry", "string", true);
501
      String method    = Utils.getDirectoryNodeString(ds, "p2j-entry", "");
502
      String hookPath  = Utils.findDirectoryNodePath(ds, "context-hook", "string", true);
503
      String hook      = Utils.getDirectoryNodeString(ds, "context-hook", "");
504
      
505
      // obtain the STOP disposition parameter:
506
      // 0 - retry without logging off
507
      // 1 - force logoff and then re-logon
508
      // 2 - force logoff
509
      int stopDisp = Utils.getDirectoryNodeInt(ds, "stop_disposition", 1);
510
      
511
      if (stopDisp < 0 || stopDisp > 2)
512
      {
513
         LOG.warning("stop_disposition configuration item is rejected: " + stopDisp);
514
         stopDisp = 1;
515
      }
516
      
517
      // last use of the directory, end our session with it
518
      ds.unbind();
519

    
520
      DatabaseManager.autoConnect();
521

    
522
      // these are used only for a 'com.foo.Bar.execute' p2j-entry case
523
      String className  = null;
524
      String methodName = null;
525

    
526
      Isolatable invoker = null;
527
      if (params.startupProc != null && params.startupProc.length() > 0)
528
      {
529
         // this is emulated via a RUN always, and that will take care to resolve any errors if the program is
530
         // not found.  just do the ACL check here.
531
         EntryPointResource epr = (EntryPointResource) sm.getPluginInstance("entrypoint");
532
         
533
         // security check is needed here because this data is user-supplied (which is an untrusted source)
534
         if (epr == null || !epr.isAllowed(params.startupProc))
535
         {
536
            if (LOG.isLoggable(Level.WARNING))
537
            {
538
               if (epr == null)
539
               {
540
                  String spec1 = "User-specified startup program '%s' cannot be accessed " +
541
                                 "because the EntryPointResource is NOT enabled.";
542
                  
543
                  LOG.logp(Level.WARNING,
544
                           "StandardServer.standardEntry",
545
                           "",
546
                           CentralLogger.generate(spec1, params.startupProc));
547
               }
548
               else
549
               {
550
                  String spec2 = "Access to user-specified startup program '%s' not " +
551
                                 "allowed for user %s.  Falling back to default.";
552
                  
553
                  LOG.logp(Level.WARNING,
554
                           "StandardServer.standardEntry",
555
                           "",
556
                           CentralLogger.generate(spec2, params.startupProc, sm.getUserId()));
557
               }
558
            }
559
            
560
            // disallow access
561
            return false;
562
         }
563
         else
564
         {
565
            invoker = new LegacyInvoker(params.startupProc);
566
         }
567
      }
568
      else
569
      {
570
         // check if there is an entry point to execute
571
         if (method.length() == 0)
572
         {
573
            throw new RuntimeException("no entry point definition found");
574
         }
575
         
576
         // resolve the p2j-entry - either a 'com.foo.Bar.execute' Java method or a legacy program name. 
577
         
578
         // try to resolve the legacy program name first
579
         String javaClass = SourceNameMapper.convertNameToClass(method);
580
         if (javaClass != null)
581
         {
582
            // the program name exists, use an emulated RUN statement
583
            invoker = new LegacyInvoker(method);
584
            
585
            // TODO: always use a LegacyInvoker
586
         }
587
         else
588
         {
589
            // TODO: this code will become obsolete and needs to be removed.
590
            
591
            // fallback to a 'com.foo.Bar.Execute' Java method
592
            
593
            // parse the definition into class name and method name
594
            int l = method.lastIndexOf(".");
595
            
596
            if (l == -1)
597
            {
598
               throw new RuntimeException("incorrect syntax for method definition " + method + " for " + 
599
                                           entryPath);
600
            }
601
            else
602
            {
603
               className = method.substring(0, l);
604
               methodName = method.substring(l + 1);
605
            }
606

    
607
            // inspect the class
608
            Class<?> cl = null;
609
            try
610
            {
611
               cl = Class.forName(className);
612
            }
613
            catch (Exception e)
614
            {
615
               // something's wrong with the class
616
               throw new RuntimeException("can't load " + className + " for " + entryPath, e);
617
            }
618

    
619
            // inspect the method
620
            Method entry = null;
621
            try
622
            {
623
               entry = cl.getMethod(methodName, (Class[]) null);
624
            }
625
            catch (Exception ex)
626
            {
627
               throw new RuntimeException(
628
                     "no method " + methodName + " defined in " + className + " for " + entryPath,
629
                     ex);
630
            }
631
            
632
            invoker = new MainInvoker(cl, entry, stopDisp);
633
         }
634
      }
635
      
636
      // load hook, if any
637
      InitTermListener listener = loadHook(hook, hookPath);
638
      
639
      // instantiate the class (if necessary) and invoke the entry point method
640
      boolean mainResult = false;
641
      
642
      Throwable t = null;
643
      
644
      try
645
      {
646
         if (listener != null)
647
         {
648
            listener.initialize();
649
         }
650
         
651
         invoke(stopDisp, invoker);
652
      }
653
      catch (InstantiationException ex)
654
      {
655
         t = ex;
656
         throw new RuntimeException("unable to instantiate " + className, ex);
657
      }
658
      catch (IllegalAccessException ex)
659
      {
660
         t = ex;
661
         throw new RuntimeException("access problem in " + methodName, ex);
662
      }
663
      catch (IllegalArgumentException ex)
664
      {
665
         t = ex;
666
         throw new RuntimeException("invalid argument to " + methodName, ex);
667
      }
668
      catch (ReflectiveOperationException ex)
669
      {
670
         t = ex;
671
         throw new RuntimeException("exception in " + methodName, ex);
672
      }
673
      catch (StopConditionException sce)
674
      {
675
         t = sce;
676
         
677
         if (stopDisp == 1)
678
         {
679
            // this value forces logoff and then re-logon (continued operation)
680
            mainResult = true;
681
         }
682
         else
683
         {
684
            // this value forces logoff (exit)
685
            mainResult = false;
686
         }
687
      }
688
      catch (UnstoppableExitException uee)
689
      {
690
         // this value forces logoff (exit)
691
         mainResult = false;
692
      }
693
      catch (RuntimeException re)
694
      {
695
         t = re;
696
         throw re;
697
      }
698
      
699
      finally
700
      {
701
         if (listener != null)
702
         {
703
            listener.terminate(t);
704
         }
705
         
706
      }
707
      
708
      return mainResult;
709
   }
710

    
711
   /**
712
    * Get the registered hook which is defined in the given jar, if any.
713
    * 
714
    * @param   jarName
715
    *          The jar file name.
716
    * 
717
    * @return  See above.
718
    */
719
   public static String getHookForJar(String jarName)
720
   {
721
      synchronized (shutdownLock)
722
      {
723
         for (InitTermListener listener : serverHooks)
724
         {
725
            if (MultiClassLoader.handledByJar(listener.getClass(), jarName))
726
            {
727
               return listener.getClass().getName();
728
            }
729
         }
730
      }
731
      
732
      return null;
733
   }
734
   
735
   /**
736
    * Terminate and remove the given server hook.
737
    * 
738
    * @param   hookClass
739
    *          The hook class name.
740
    */
741
   public static void removeHook(String hookClass)
742
   {
743
      synchronized (shutdownLock)
744
      {
745
         Iterator<InitTermListener> iter = serverHooks.iterator();
746
         while (iter.hasNext())
747
         {
748
            InitTermListener listener = iter.next();
749
            
750
            if (listener.getClass().getName().equals(hookClass))
751
            {
752
               listener.terminate(null);
753
               iter.remove();
754

    
755
               break;
756
            }
757
         }
758
      }
759
   }
760
   
761
   /**
762
    * Add server hook which will be notified about server startup and shutdown. 
763
    * 
764
    * @param    hook
765
    *           Server hook to add.
766
    */
767
   public static void register(InitTermListener hook)
768
   {
769
      synchronized (shutdownLock)
770
      {
771
         serverHooks.add(hook);
772
      }
773
   }
774

    
775
   /**
776
    * Load {@link InitTermListener} hook using specified class and path.
777
    *
778
    * @param    hook
779
    *           Full class name.
780
    * @param    hookPath
781
    *           Hook path in directory.
782
    *
783
    * @return   Instance of loaded hook if any.
784
    */
785
   public static InitTermListener loadHook(String hook, String hookPath)
786
   {
787
      InitTermListener listener = null;
788

    
789
      if (hook != null && hook.length() > 0)
790
      {
791
         Class<?> hookCl = null;
792

    
793
         try
794
         {
795
            // can not use Class.forName, as it caches the loaded class in
796
            // some native code from which it can never be removed
797
            hookCl = ClassLoader.getSystemClassLoader().loadClass(hook);
798
         }
799
         catch (Exception e)
800
         {
801
            // something's wrong with the hook class
802
            throw new RuntimeException("can't load " + hook + " for "
803
                                       + hookPath,
804
                                       e);
805
         }
806

    
807
         if (!InitTermListener.class.isAssignableFrom(hookCl))
808
            // something's wrong with the hook class
809
            throw new RuntimeException("class " + hook + " must implement "
810
                                       + InitTermListener.class.getSimpleName()
811
                                       + " interface.");
812

    
813
         try
814
         {
815
            listener = (InitTermListener) hookCl.getDeclaredConstructor().newInstance();
816
         }
817
         catch (InstantiationException e)
818
         {
819
            throw new RuntimeException("unable to instantiate " + hook, e);
820
         }
821
         catch (InvocationTargetException e)
822
         {
823
            throw new RuntimeException("wrapped exception in " + hook + " constructor", e.getTargetException());
824
         }
825
         catch (NoSuchMethodException e)
826
         {
827
            throw new RuntimeException("access problem in " + hook + " constructor", e);
828
         }
829
         catch (SecurityException e)
830
         {
831
            throw new RuntimeException("security problem in " + hook + " constructor", e);
832
         }
833
         catch (IllegalAccessException e)
834
         {
835
            throw new RuntimeException("access problem in " + hook + " constructor", e);
836
         }
837
         catch (IllegalArgumentException e)
838
         {
839
            throw new RuntimeException("argument problem in " + hook + " constructor", e);
840
         }
841
      }
842
      
843
      return listener;
844
   }
845
   
846
   /**
847
    * Get the parameters exposed by the client-side exposed, for this context.
848
    * 
849
    * @return   A {@link ClientParameters} instance, with the context's parameters.
850
    */
851
   public static ClientParameters getClientParameters()
852
   {
853
      return localParams.get();
854
   }
855
   
856
   /**
857
    * Return the paths, relative to the server starting directory, of jars from which resources
858
    * are to be loaded. This will be a subset of the server process' classpath.
859
    * 
860
    * @return  List of resource jar paths.
861
    */
862
   public static List<String> getResourceJarPaths()
863
   {
864
      return resourceJarPaths;
865
   }
866
   
867
   /**
868
    * Block the calling thread until the non-network portions of the server
869
    * are fully initialized.  At that point the server is ready to execute
870
    * real logic.
871
    */
872
   public void waitUntilReady()
873
   throws InterruptedException
874
   {
875
      ready.await();
876
   }
877
   
878
   /**
879
    * Bootstrap the server environment by initializing network services,
880
    * exporting all standard entry points for this server, loading all
881
    * application specific startup classes, and starting the server socket
882
    * listening loop.
883
    * <p>
884
    * The implementation looks up the directory under the path
885
    * <code>/server/serverID/exports</code> to figure out the subtree of
886
    * registrations.
887
    * <p>
888
    * If no such subtree exists, <code>/server/default/exports</code> is
889
    * checked next.
890
    * <p>
891
    * Entries have the format <code>export-group-ID/export-method-ID</code>
892
    * where the <code>export-group-ID</code> is a container naming a group and
893
    * <code>export-method-ID</code> is an object naming the export and
894
    * providing its implementation location through its string type "value"
895
    * attribute as <code>class-name.method-name(signature)</code>.
896
    * <p>
897
    * <code>method-name</code> should refer to a public method of the class.
898
    * It can be either a static or instance method. The class is instantiated
899
    * once for the first instance method reference.
900
    * <p>
901
    * Specifying a method without signature is allowed if the method is not
902
    * overloaded.
903
    * <p>
904
    * Once all the generic directory-driven exports are processed, then all
905
    * the following are registered using the simpler <code>RemoteObject</code>
906
    * mechanism:
907
    * <p>
908
    * 
909
    * <pre>
910
    * Interface         Type        Target
911
    * ----------------- ----------- ----------------------------------
912
    * ServerExports     static      LogicalTerminal
913
    * RemoteErrorData   static      ErrorManager
914
    * MainEntry         static      StandardServer
915
    * </pre>
916
    * <p>
917
    * When basic initialization is finished, this method registers default
918
    * services and then loads user defined services if they are defined in
919
    * <b>startups</b> directory entry under either
920
    * <code>/server/serverID</code> or <code>/server/default</code> entry.
921
    * <p>
922
    * User defined services must implement {@link InitTermListener} interface
923
    * and its {@link InitTermListener#initialize()} will be invoked right
924
    * before server will start listen for incoming connections while its
925
    * {@link InitTermListener#terminate(Throwable)} method will be invoked
926
    * when server stopped accepting incoming connections and all active
927
    * sessions are terminated. If server is stopped in graceful way (via
928
    * remote request) then <code>null</code> is passed as a parameter into
929
    * {@link InitTermListener#terminate(Throwable)} method. If server is
930
    * stopped in abnormal way, for example, because user typed Ctrl-C, then
931
    * some non-null value is passed as a parameter. Passed value should not be
932
    * relied on to determine real cause of server stop.
933
    * 
934
    * @param   config
935
    *          Bootstrap configuration information for this server.
936
    * @param   diagnostics
937
    *          Details about the driver state that can help diagnose server startup issues.
938
    * @param   cfgProfile
939
    *          The optional configuration profile.
940
    *           
941
    * @throws  Exception
942
    *          Various exceptions generated by invalid configurations.
943
    */
944
   public void bootstrap(BootstrapConfig config, Supplier<String> diagnostics, String cfgProfile)
945
   throws Exception
946
   {
947
      // set a global property to for Antlr to use Class.forName
948
      System.setProperty("ANTLR_USE_DIRECT_CLASS_LOADING", "true");
949
      
950
      LOG.info(Version.getFWDVersion() + " Server starting initialization." );
951
      LOG.info(diagnostics.get());
952
      LOG.info("BootstrapConfig " + config.toString());
953
      
954
      Configuration.setRuntimeConfig(true);
955
      
956
      // instantiate the directory service with must_exist option
957
      config.setConfigItem("directory", "xml", "must_exist", "true");
958
      DirectoryService ds = DirectoryService.createInstance(config);
959
      
960
      // bind to the directory
961
      ds.bind();
962
      
963
      // instantiate security manager
964
      final SecurityManager sm     = SecurityManager.createInstance(config);
965
      final SessionManager sessMgr = SessionManagerFactory.createRouterNode(config);
966
      
967
      // initialize the class loaders for the jars set in the directory
968
      initClassLoaders(ds);
969
      
970
      // enumerate exported groups
971
      String path = Utils.findDirectoryNodePath(ds, "exports", "container", false);
972
      String[] groups = ds.enumerateNodes(path);
973
      
974
      if (groups == null || groups.length == 0)
975
         LOG.info("No exported entry points defined in the P2J directory");
976
      
977
      // put instance references into the map for these special cases
978
      Class<?> cl = ds.getClass();
979
      inst.put(cl.getName(), ds);       // reference to DirectoryService
980
      cl = sm.getClass();
981
      inst.put(cl.getName(), sm);       // reference to SecurityManager
982
      cl = this.getClass();
983
      inst.put(cl.getName(), this);     // reference to StandardServer
984
      
985
      if (groups != null)
986
      {
987
         // process groups
988
         for (String group : groups)
989
         {
990
            // enumerate exports for this group
991
            String methodPath = path + "/" + group;
992
            String[] methods = ds.enumerateNodes(methodPath);
993
            
994
            if (methods == null)
995
               continue;
996
            
997
            // process exports
998
            for (String s : methods)
999
            {
1000
               // read the method specification
1001
               String valuePath = methodPath + "/" + s;
1002
               String method = ds.getNodeString(valuePath, "value");
1003
               
1004
               // register method
1005
               if (exportMethod(group, s, method))
1006
                  LOG.finer(" exported " + group + ":" + s);
1007
               else
1008
                  LOG.finer("export failed " + group + ":" + s);
1009
            }
1010
         }
1011
      }
1012
      
1013
      // simple form (not directory based)
1014
      Class<?>[] ifaces = new Class[] { ServerExports.class };
1015
      RemoteObject.registerStaticNetworkServer(ifaces, LogicalTerminal.class);
1016
      
1017
      ifaces = new Class[] { RemoteErrorData.class };
1018
      RemoteObject.registerStaticNetworkServer(ifaces, ErrorManager.class);
1019
      
1020
      ifaces = new Class[] { MainEntry.class };
1021
      RemoteObject.registerStaticNetworkServer(ifaces, StandardServer.class);
1022
      
1023
      ifaces = new Class[] { AppServerEntry.class };
1024
      RemoteObject.registerStaticNetworkServer(ifaces, AppServerManager.class);
1025
      
1026
      ifaces = new Class[] { AppServer.class };
1027
      RemoteObject.registerStaticNetworkServer(ifaces, AppServerLauncher.class);
1028
      
1029
      ifaces = new Class[] { BatchProcess.class };
1030
      RemoteObject.registerStaticNetworkServer(ifaces, ProcessClientSpawner.class);
1031
      
1032
      ifaces = new Class[] { RemoteSpawner.class };
1033
      RemoteObject.registerStaticNetworkServer(ifaces, ClientBuilder.class);
1034
      
1035
      ifaces = new Class[] { BrokerServerServices.class };
1036
      RemoteObject.registerStaticNetworkServer(ifaces, BrokerManager.class);
1037
      
1038
      ifaces = new Class[] { ServerPropertiesInspector.class };
1039
      RemoteObject.registerStaticNetworkServer(ifaces, ServerPropertiesDaemon.class);
1040

    
1041
      ifaces = new Class[] { CentralLogService.class };
1042
      RemoteObject.registerStaticNetworkServer(ifaces, CentralLogStaticService.class);
1043
   
1044
      RemoteObject.registerStaticNetworkServer(new Class[] { UnitTestEngine.class }, UnitTestServer.class);
1045
      
1046
      AdminServerImpl.initialize();
1047
      
1048
      DirectoryManager.initServer();
1049
      
1050
      // set the optional configuration profile must be called after initialization of Configuration
1051
      // and DirectoryManager
1052
      if (cfgProfile != null)
1053
      {
1054
         Configuration.setDefaultProfile(cfgProfile);
1055
      }
1056
      
1057
      registerDefaultServices(sm, config);
1058
      
1059
      AstManager.initialize(() -> new InMemoryRegistryPlugin());
1060
      
1061
      Throwable poolExc = null;
1062
      try
1063
      {
1064
         ConversionPool.initialize();
1065
      }
1066
      catch (RuntimeException exc)
1067
      {
1068
         poolExc = exc;
1069
      }
1070
      catch (ConfigurationException exc)
1071
      {
1072
         poolExc = exc.getCause();
1073
      }
1074
      finally
1075
      {
1076
         if (poolExc != null)
1077
         {
1078
            // not all apps use runtime conversion services, so this is not a fatal error, but
1079
            // log a warning
1080
            if (LOG.isLoggable(Level.WARNING))
1081
            {
1082
               LOG.log(Level.WARNING,
1083
                       "Unable to initialize runtime conversion pool; if dynamic database " +
1084
                       "features are not in use, this warning can be ignored safely (otherwise " +
1085
                       "restart server with FINE logging for additional information)");
1086
               if (LOG.isLoggable(Level.FINE))
1087
               {
1088
                  LOG.log(Level.FINE,
1089
                          "Ensure configuration items for runtime conversion are stored in the " +
1090
                          "application jar file",
1091
                          poolExc);
1092
               }
1093
            }
1094
         }
1095
      }
1096
      
1097
      // set the interrupt handler in the session manager.
1098
      sessMgr.setInterruptHandler(new InterruptedExceptionHandler());
1099
      
1100
      // load the standard startup classes (must be after the dispatcher,
1101
      // directory, security manager are all initialized)
1102
      loadStartup(ds);
1103
      
1104
      // initialize the list of jar files from which to load resources
1105
      initResourceJarPaths(ds);
1106
      
1107
      hookInitialize();
1108
      
1109
      Runtime.getRuntime().addShutdownHook(new Thread(() -> {
1110
         Throwable t = new Throwable();
1111
         
1112
         try
1113
         {
1114
            sm.setInitialSecurityContext();
1115
            sessMgr.waitForShutdown();
1116
         }
1117
         catch (Throwable e)
1118
         {
1119
            t = e;
1120
         }
1121
         
1122
         hookTerminate(t);
1123
      }));
1124

    
1125
      long n1 = System.nanoTime();
1126
      for (int i = 0; i < 1_000_000; i++)
1127
      {
1128
         LOG.log(Level.WARNING, "something");
1129
         // LOG2.log(Level.WARNING, "something");
1130
      }
1131
      long n2 = System.nanoTime();
1132
      System.out.println("centralLogger " + (n2 - n1) / 1_000_000L);
1133

    
1134
      Logger rootLogger = Logger.getLogger("");
1135
      ConsoleHandler consoleHandler = new ConsoleHandler();
1136
      consoleHandler.setFormatter(new CleanFormatter());
1137
      rootLogger.addHandler(consoleHandler);
1138
      
1139
      Logger anotherLogger = Logger.getLogger("random");
1140

    
1141
      n1 = System.nanoTime();
1142
      for (int i = 0; i < 1_000_000; i++)
1143
      {
1144
         anotherLogger.log(Level.WARNING, "something");
1145
      }
1146
      n2 = System.nanoTime();
1147
      System.out.println("java console logger " + (n2 - n1) / 1_000_000L);
1148
      
1149
      try
1150
      {
1151
         // wake up any waiting threads, the non-network portions of the
1152
         // server are fully initialized now
1153
         ready.countDown();
1154
         
1155
         SessionListener webClientsManager;
1156
         if (Utils.getDirectoryNodeBoolean(ds, "webClient/enabled", false, false))
1157
         {
1158
            webClientsManager = WebClientsManager.getInstance();
1159
         }
1160
         else
1161
         {
1162
            webClientsManager = null;
1163
         }
1164
         
1165
         // main listening loop;  service incoming socket connections
1166
         sessMgr.listen(webClientsManager);
1167
      }
1168
      finally
1169
      {
1170
         sessMgr.waitForShutdown();
1171
         hookTerminate(null);
1172
      }
1173
   }
1174

    
1175

    
1176
   /**
1177
    * Simple formatter to emit a very clean, condensed output.
1178
    */
1179
   public static class CleanFormatter
1180
      extends Formatter
1181
   {
1182
      /** Date format string. */
1183
      private static final String FMT = "MM/dd/yyyy HH:mm:ss z";
1184

    
1185
      /** Date and time formatter. */
1186
      private static final ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>()
1187
      {
1188
         @Override
1189
         protected SimpleDateFormat initialValue()
1190
         {
1191
            return new SimpleDateFormat(FMT);
1192
         }
1193
      };
1194

    
1195
      /** Platform-specific line separator. */
1196
      private static final String SEP = System.getProperty("line.separator");
1197

    
1198
      /**
1199
       * Format the given log record and return the formatted string.
1200
       *
1201
       * @param    record
1202
       *           The log record to be formatted. 
1203
       *
1204
       * @return   The formatted log record.
1205
       */
1206
      public String format(LogRecord record)
1207
      {
1208
         String spec = "[%s] (%s:%s) %s%s";
1209

    
1210
         Date dat = new Date(record.getMillis());
1211

    
1212
         Object[] parms = new Object[]
1213
            {
1214
               sdf.get().format(dat),
1215
               record.getSourceClassName(),
1216
               record.getLevel().toString(),
1217
               record.getMessage(),
1218
               SEP
1219
            };
1220

    
1221
         String out = String.format(spec, parms);
1222

    
1223
         Throwable t = record.getThrown();
1224

    
1225
         if (t != null)
1226
         {
1227
            StringBuilder sb = new StringBuilder(out);
1228
            dumpThrowable(t, sb);
1229
            out = sb.toString();
1230
         }
1231

    
1232
         return out;
1233
      }
1234

    
1235
      /**
1236
       * Dump all details of the throwable into the given string buffer.
1237
       * Includes exception type, message, complete stack trace, and chained
1238
       * throwables, if any.
1239
       *
1240
       * @param    t
1241
       *           The throwable to dump.
1242
       * @param    buf
1243
       *           String buffer into which details are written.
1244
       */
1245
      private void dumpThrowable(Throwable t, StringBuilder buf)
1246
      {
1247
         Throwable cause = t;
1248
         do
1249
         {
1250
            if (cause != t)
1251
            {
1252
               buf.append("Caused by: ");
1253
            }
1254

    
1255
            buf.append(cause.getClass().getName());
1256
            String msg = cause.getMessage();
1257
            if (msg != null && msg.trim().length() > 0)
1258
            {
1259
               buf.append(": ");
1260
               buf.append(msg);
1261
            }
1262
            buf.append(SEP);
1263

    
1264
            StackTraceElement[] trace = cause.getStackTrace();
1265

    
1266
            for (int i = 0; i < trace.length; i++)
1267
            {
1268
               buf.append("        at ");
1269
               buf.append(trace[i]);
1270
               buf.append(SEP);
1271
            }
1272

    
1273
            cause = cause.getCause();
1274
         }
1275
         while (cause != null);
1276
      }
1277
   }
1278
   
1279
   /**
1280
    * Initialize the class loaders for all the jars defined in the directory.
1281
    * This can be done only if the <code>java.system.class.loader</code>
1282
    * property is set to the {@link MultiClassLoader custom class loader}.
1283
    * 
1284
    * @param   ds
1285
    *          The directory service.
1286
    */
1287
   private static void initClassLoaders(DirectoryService ds)
1288
   {
1289
      // initialize the class loaders for the jars defined in the directory
1290
      String path = Utils.findDirectoryNodePath(ds,
1291
                                                "jars",
1292
                                                "strings",
1293
                                                false);
1294
      if (path == null)
1295
      {
1296
         return;
1297
      }
1298

    
1299
      String[] jars = ds.getNodeStrings(path, "values");
1300
      if (jars != null && jars.length > 0)
1301
      {
1302
         ClassLoader scl = ClassLoader.getSystemClassLoader();
1303
         if (scl instanceof MultiClassLoader)
1304
         {
1305
            try
1306
            {
1307
               for (String jar : jars)
1308
               {
1309
                  if (MultiClassLoader.addJarLoader(jar) != ErrorCode.SUCCESS)
1310
                  {
1311
                     // we need to stop the server, as it could not initialize
1312
                     // properly
1313
                     throw new RuntimeException(
1314
                        "Could not initialize class loader for jar " +
1315
                              jar + " !");
1316
                  }
1317
               }
1318
            }
1319
            catch (RestrictedUseException e)
1320
            {
1321
               throw new IllegalStateException("Call of " +
1322
                  "MultiClassLoader.addJarLoader from this function should" +
1323
                  "be granted.");
1324
            }
1325
         }
1326
         else
1327
         {
1328
            if (LOG.isLoggable(Level.SEVERE))
1329
            {
1330
               String msg = "Jars are defined in the directory, but the " +
1331
                          "java.system.class.loader is not an instance of " +
1332
                          "com.goldencode.p2j.classloader.MultiClassLoader !";
1333
               
1334
               LOG.logp(Level.SEVERE,
1335
                        "StandardServer.initClassLoaders",
1336
                        "",
1337
                        msg);
1338
            }
1339
         }
1340
      }
1341
   }
1342
   
1343
   /**
1344
    * Register default services:
1345
    * <ol>
1346
    * <li> {@link Persistence} </li>
1347
    * <li> {@link WebServer} </li>
1348
    * <li> custom account extension plugin </li>
1349
    * </ol>
1350
    *
1351
    * @param    sm
1352
    *           The initialized instance of <code>SecurityManager</code>.
1353
    * @param    config
1354
    *           The bootstrap configuration information for this server.
1355
    */
1356
   private void registerDefaultServices(final SecurityManager sm,
1357
                                        final BootstrapConfig config)
1358
   {
1359
      serverHooks.add(new AbstractInitTermListener()
1360
      {
1361
         @Override
1362
         public void initialize()
1363
         {
1364
            // initialize broker manager.
1365
            // read broker configurations from directory.
1366
            BrokerManager.initialize();
1367
         }
1368

    
1369
         @Override
1370
         public void terminate(Throwable t)
1371
         {
1372
            // close brokers sessions
1373
            BrokerManager.cleanup();
1374
         }
1375
      });
1376

    
1377
      serverHooks.add(new AbstractInitTermListener()
1378
      {
1379
         @Override
1380
         public void initialize()
1381
         {
1382
            // schedule the jobs to be launched; this will wait for the server to open its 
1383
            // secure and insecure sockets (depending on configuration)
1384
            Scheduler.scheduleJobs();
1385
         }
1386
      });
1387

    
1388
      serverHooks.add(new AbstractInitTermListener()
1389
      {
1390
         @Override
1391
         public void initialize()
1392
         {
1393
            // after scheduler
1394
            ProcedureManager.initialize();
1395
         }
1396
      });
1397
      
1398
      serverHooks.add(new AbstractInitTermListener()
1399
      {
1400
         @Override
1401
         public void initialize()
1402
         {
1403
            // client options must be loaded on a thread with the server context
1404
            WebClientBuilderOptions.initialize(sm, config);
1405
         }
1406
      });
1407

    
1408
      serverHooks.add(new AbstractInitTermListener()
1409
      {
1410
         @Override
1411
         public void initialize()
1412
         {
1413
            ServerKeyStore.initialize();
1414
         }
1415
      });
1416
      
1417
      // On this step we can initialize the server side of the ErrorWriter inside the
1418
      // ErrorManager. It is important to do this exactly after the LogicalTerminal setup is
1419
      // complete. Do not move this cal agead. 
1420
      ErrorManager.initErrorWriter(new ErrorWriterServer());
1421
      
1422
      serverHooks.add(new AbstractInitTermListener()
1423
      {
1424
         @Override
1425
         public void initialize()
1426
         {
1427
            if (!Utils.getDirectoryNodeBoolean(null, "persistence/active", true, false))
1428
            {
1429
               LOG.warning("Server persistence is inactive. Running applications with " +
1430
                           "persistence dependency may fail.");
1431
               return;
1432
            }
1433
            
1434
            try
1435
            {
1436
               // initialize database statistics; must be called before persistence is
1437
               // initialized, because DatabaseManager will register with DatabaseStatistics
1438
               // when registering auto-connect databases
1439
               DatabaseStatistics.get();
1440
               
1441
               Persistence.initialize();
1442
            }
1443
            catch (PersistenceException e)
1444
            {
1445
               throw new RuntimeException(e);
1446
            }
1447
         }
1448
         
1449
         @Override
1450
         public void terminate(Throwable t)
1451
         {
1452
            if (DatabaseStatistics.isEnabled())
1453
            {
1454
               DatabaseStatistics.get().collectStatistics();
1455
            }
1456
         }
1457
      });
1458
      
1459
      serverHooks.add(new AbstractInitTermListener()
1460
      {
1461
         @Override
1462
         public void initialize()
1463
         {
1464
            SourceNameMapper.initialize();
1465
         }
1466
      });
1467
      
1468
      // register controller for method execution tracing
1469
      serverHooks.add(new AbstractInitTermListener()
1470
      {
1471
         @Override
1472
         public void initialize()
1473
         {
1474
            Class<?>[] ifaces = new Class[] { MethodTraceController.class };
1475
            RemoteObject.registerStaticNetworkServer(ifaces, MethodTraceController.Impl.class);
1476
         }
1477
      });
1478
      
1479
      // don't start the web service unless we're in a secure environment
1480
      boolean isSecure = config.getBoolean("net", "connection", "secure", false);
1481
      
1482
      if (isSecure)
1483
      {
1484
         serverHooks.add(new AbstractInitTermListener()
1485
         {
1486
            @Override
1487
            public void initialize()
1488
            {
1489
               // Initialize pool
1490
               TemporaryAccountPool.getInstance();
1491
            }
1492
            
1493
            @Override
1494
            public void terminate(Throwable t)
1495
            {
1496
               // Kill worker thread
1497
               TemporaryAccountPool.shutDown();
1498
            }
1499
         });
1500
         
1501
         // FontTable initialization
1502
         serverHooks.add(new AbstractInitTermListener()
1503
         {
1504
            @Override
1505
            public void initialize()
1506
            {
1507
               FontTable.initialize();
1508
            }
1509
         });
1510
         
1511
         serverHooks.add(new AbstractInitTermListener()
1512
         {
1513
            /** Flag to determine if the embedded mode app is active. */
1514
            boolean embedded = false;
1515
            
1516
            /** Flag to determine if the REST services are active. */
1517
            boolean rest = false;
1518
            
1519
            /** Flag to determine if the web handler is active. */
1520
            boolean webHandler = false;
1521
            
1522
            boolean soap       = false;
1523

    
1524
            @Override
1525
            public void initialize()
1526
            {
1527
               int     num        = 0;
1528
               boolean adm        = false;
1529
               boolean webui      = false;
1530
               
1531
               if (Utils.getDirectoryNodeBoolean(null, "adminEnabled", false, false))
1532
               {
1533
                  // admin
1534
                  num++;
1535
                  adm = true;
1536
               }
1537
               
1538
               if (Utils.getDirectoryNodeBoolean(null, "webClient/enabled", false, false))
1539
               {
1540
                  num++;
1541
                  webui = true;
1542
               }
1543
               
1544
               if (Utils.getDirectoryNodeBoolean(null, "embeddedWebApp/enabled", false, false))
1545
               {
1546
                  num++;
1547
                  embedded = true;
1548
               }
1549
               
1550
               if (Utils.getDirectoryNodeBoolean(null, "rest/enabled", false, false))
1551
               {
1552
                  num++;
1553
                  rest = true;
1554
               }
1555
               
1556
               if (Utils.getDirectoryNodeBoolean(null, "soap/enabled", false, false))
1557
               {
1558
                  num++;
1559
                  soap = true;
1560
               }
1561

    
1562
               if (Utils.getDirectoryNodeBoolean(null, "webHandler/enabled", false, false))
1563
               {
1564
                  num++;
1565
                  webHandler = true;
1566
               }
1567

    
1568
               ContextHandler[] webContentHandlers = null;
1569
               try
1570
               {
1571
                  webContentHandlers = WebServer.getWebContentHandlers();
1572
                  num += webContentHandlers.length;
1573
               }
1574
               catch(Throwable ex)
1575
               {
1576
                  LOG.severe("", ex);
1577
               }
1578
               
1579
               if (num == 0)
1580
                  return;
1581
               
1582
               final org.eclipse.jetty.server.Handler[] handlers =
1583
                  new org.eclipse.jetty.server.Handler[num];
1584
               
1585
               // admin
1586
               if (adm)
1587
               {
1588
                  Resource resourceBase = Resource.newClassPathResource("com/goldencode/p2j/admin");
1589

    
1590
                  String path = "/adminapp";
1591
                  Class servletExtClass = null;
1592
                  String[] welcomeFile = new String[] {"AdminApp.html"};
1593

    
1594
                  if (adminServerExtension != null)
1595
                  {
1596
                     path = adminServerExtension.modulePath;
1597
                     if (adminServerExtension.adminClientResourcePath != null)
1598
                     {
1599
                        Resource res = Resource.newClassPathResource(adminServerExtension.adminClientResourcePath);
1600
                        resourceBase = new TwoURLsResource(res, resourceBase);
1601
                     }
1602

    
1603
                     servletExtClass = adminServerExtension.adminExtensionServlet;
1604
                     welcomeFile[0] = adminServerExtension.welcomeFile;
1605
                  }
1606

    
1607
                  WebAppContext webApp = new WebAppContext("adminapp", "/admin");
1608
                  webApp.getSessionHandler().setMaxInactiveInterval(20);
1609
                  webApp.setBaseResource(resourceBase);
1610
                  webApp.addServlet(AdminServiceImpl.class, path + "/AdminService");
1611
                  webApp.addServlet(AuthServiceImpl.class, path + "/AuthService");
1612
                  webApp.addServlet(ReportServlet.class, path + "/ReportService");
1613

    
1614
                  // prevent directory listing
1615
                  webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed",
1616
                                          "false");
1617

    
1618
                  // admin server extension
1619
                  if (servletExtClass != null)
1620
                  {
1621
                     String pathSpec = path + adminServerExtension.servletPath;
1622
                     webApp.addServlet(servletExtClass, pathSpec);
1623

    
1624
                     webApp.addFilter(SynchronizeFilter.class, pathSpec,
1625
                                      EnumSet.allOf(DispatcherType.class));
1626
                     webApp.addFilter(AuthFilter.class, pathSpec,
1627
                                      EnumSet.allOf(DispatcherType.class));
1628
                  }
1629

    
1630
                  webApp.addFilter(SynchronizeFilter.class,
1631
                                   path + "/AdminService",
1632
                                   EnumSet.allOf(DispatcherType.class));
1633
                  webApp.addFilter(SynchronizeFilter.class,
1634
                                   path + "/ReportService",
1635
                                   EnumSet.allOf(DispatcherType.class));
1636
                  webApp.addFilter(AuthFilter.class,
1637
                                   path + "/AdminService",
1638
                                   EnumSet.allOf(DispatcherType.class));
1639
                  webApp.addFilter(AuthFilter.class,
1640
                                   path + "/ReportService",
1641
                                   EnumSet.allOf(DispatcherType.class));
1642

    
1643
                  webApp.setWelcomeFiles(welcomeFile);
1644
                  webApp.setParentLoaderPriority(true);
1645
                  String adminTmp = Utils.getOrCreateTemporaryDirectory("fwdadmin" + new Random().nextLong());
1646
                  webApp.setTempDirectory(new File(adminTmp));
1647
                  
1648
                  HandlerList list = new HandlerList();
1649
                  list.setHandlers(new org.eclipse.jetty.server.Handler[] {
1650
                     webApp,
1651
                     new DefaultHandler()
1652
                  });
1653

    
1654
                  handlers[--num] = list;
1655
               }
1656
                             
1657
               if (webui)
1658
               {
1659
                  handlers[--num] = new WebHandler();
1660
                  
1661
                  // export the API for web client launching
1662
                  Class<?>[] ifaces = new Class[] { WebClientLauncher.class };
1663
                  RemoteObject.registerStaticNetworkServer(ifaces, WebHandler.class);
1664
               }
1665
               
1666
               if (embedded)
1667
               {
1668
                  handlers[--num] = EmbeddedWebAppHandler.initialize();
1669
               }
1670
               
1671
               if (rest)
1672
               {
1673
                  handlers[--num] = RestHandler.initialize();
1674
               }
1675
               
1676
               if (soap)
1677
               {
1678
                  handlers[--num] = SoapHandler.initialize();
1679
               }
1680

    
1681
               if (webHandler)
1682
               {
1683
                  handlers[--num] = WebServiceHandler.initialize();
1684
               }
1685
               
1686
               if (rest || webHandler || soap)
1687
               {
1688
                  // force loading of the services...
1689
                  SourceNameMapper.registerServices();
1690
                  
1691
                  if (RestHandler.useWebServiceAuthentication() || 
1692
                      SoapHandler.useWebServiceAuthentication() ||
1693
                      WebServiceHandler.useWebServiceAuthentication())
1694
                  {
1695
                     sm.startWebServiceContextThreads();
1696
                  }
1697
               }
1698
               
1699
               if (webContentHandlers != null)
1700
               {
1701
                  System.arraycopy(webContentHandlers, 0, handlers, 0, webContentHandlers.length);
1702
               }
1703
               
1704
               WebServer.initialize(handlers);
1705

    
1706
               if (soap)
1707
               {
1708
                  SoapHandler.initializePost();
1709
               }
1710
            }
1711
            
1712
            @Override
1713
            public void terminate(Throwable t)
1714
            {
1715
               WebServer.terminate();
1716

    
1717
               if (embedded)
1718
               {
1719
                  EmbeddedWebAppHandler.terminate();
1720
               }
1721
               
1722
               if (rest)
1723
               {
1724
                  RestHandler.terminate();
1725
               }
1726
               
1727
               if (soap)
1728
               {
1729
                  SoapHandler.terminate();
1730
               }
1731

    
1732
               if (webHandler)
1733
               {
1734
                  WebServiceHandler.terminate();
1735
               }
1736
            }
1737
         });
1738
      }
1739
      
1740
      // load custom account extension server-side plugin
1741
      String customExt = sm.getCustomServerExt();
1742
      
1743
      if (customExt == null)
1744
      {
1745
         return;
1746
      }
1747
      
1748
      Class<?> cext = null;
1749
      
1750
      try
1751
      {
1752
         cext = Class.forName(customExt);
1753
      }
1754
      
1755
      catch (ClassNotFoundException cnfe)
1756
      {
1757
         LOG.severe("server extension class not found", cnfe);
1758
         return;
1759
      }
1760
      
1761
      final Method minit;
1762
      
1763
      try
1764
      {
1765
         minit = cext.getMethod("initialize", (Class[])null);
1766
      }
1767
      
1768
      catch (Exception e)
1769
      {
1770
         LOG.severe("server extension class has no initialize() method", e);
1771
         return;
1772
      }
1773
      
1774
      serverHooks.add(new AbstractInitTermListener()
1775
      {
1776
         @Override
1777
         public void initialize()
1778
         {
1779
            try
1780
            {
1781
               minit.invoke(null, (Object[])null);
1782
               LOG.fine("server extension plugin initialized");
1783
            }
1784
            catch (Exception e)
1785
            {
1786
               LOG.severe("server extension class failed to initialize", e);
1787
               return;
1788
            }
1789
         }
1790
      });
1791

    
1792
      try
1793
      {
1794
         Method m = cext.getMethod("getAdminExtension", (Class[])null);
1795
         adminServerExtension = (AdminServerExtension) m.invoke(null, (Object[]) null);
1796
      }
1797
      catch (NoSuchMethodException e)
1798
      {
1799
         // the method is optional, so ignore
1800
      }
1801
      catch (Exception e)
1802
      {
1803
         LOG.severe("getServletClass call failed on server extension class", e);
1804
      }
1805
   }
1806
   
1807
   /**
1808
    * Initializes the <code>TransactionManager</code> environment and other
1809
    * user-specific runtime initialization and then executes the given entry
1810
    * point.
1811
    *
1812
    * @param    stopDisp
1813
    *           STOP condition disposition.
1814
    *           <ul>
1815
    *             <li>0 - retry without logging off
1816
    *             <li>1 - force logoff and then re-logon
1817
    *             <li>2 - force logoff
1818
    *           </ul>
1819
    *
1820
    * @param    entry
1821
    *           instance of the Isolatable interface that provides the entry
1822
    *           point to be executed
1823
    *
1824
    * @return   The result of the invocation.  If the signature of the method
1825
    *           specifies a <code>void</code> return then this will return
1826
    *           <code>null</code>.  Note that it is a limitation of the
1827
    *           design of Java method signatures such that we do not
1828
    *           distinguish between a method with a <code>void</code> return
1829
    *           and a method that has a real return value which is set to
1830
    *           <code>null</code>.
1831
    *           
1832
    * @throws   NullPointerException 
1833
    * @throws   IllegalAccessException 
1834
    * @throws   IllegalArgumentException 
1835
    * @throws   InvocationTargetException 
1836
    * @throws   InstantiationException 
1837
    */
1838
   public static Object invoke(int stopDisp, Isolatable entry)
1839
   throws NullPointerException,
1840
          IllegalAccessException,
1841
          InvocationTargetException,
1842
          InstantiationException
1843
   {
1844
      // simple test for a quick out
1845
      if (entry == null)
1846
      {
1847
         throw new NullPointerException("Specified entry point is null!");
1848
      }
1849

    
1850
      // force DatabaseTriggerManager context-local instantiation before the new scope is pushed
1851
      DatabaseTriggerManager.get();
1852

    
1853
      // add the topmost scope to the TransactionManager
1854
      TransactionManager.pushScope("startup",
1855
                                   TransactionManager.NO_TRANSACTION,
1856
                                   true,
1857
                                   true,
1858
                                   false,
1859
                                   false);
1860
      
1861
      // make sure that the global block gets a special notification - but only for non-appserver sessions.
1862
      LogicalTerminal.registerCleaner();
1863
      
1864
      Object result = null;
1865
      try
1866
      {
1867
         startup:
1868
         {
1869
            TransactionManager.blockSetup();
1870
            
1871
            do
1872
            {
1873
               try
1874
               {
1875
                  // calling the entry point
1876
                  result = entry.execute();
1877
               }
1878
               
1879
               catch (StopConditionException sce)
1880
               {
1881
                  if (stopDisp != 0)
1882
                  {
1883
                     if (stopDisp == 1)
1884
                        LogicalTerminal.setRelogin(true);
1885
         
1886
                     throw sce;
1887
                  }
1888
                  
1889
                  TransactionManager.disableLoopProtection(null);
1890
                  TransactionManager.triggerRetry(null);
1891
               }
1892
               
1893
               catch (ConditionException ce)
1894
               {
1895
                  if (ce instanceof QuitConditionException)
1896
                  {
1897
                     TransactionManager.setProcessingQuit(true);
1898
                  }
1899
                  break startup;
1900
               }
1901
               
1902
               catch (RetryUnwindException ru)
1903
               {
1904
                  TransactionManager.honorRetry(ru);
1905
               }
1906
               
1907
               catch (Throwable th)
1908
               {
1909
                  Throwable chained = th;
1910
                  
1911
                  boolean   isError = false;
1912
                  boolean   isStop  = false;
1913
                  
1914
                  // look through all causes to determine if there was an
1915
                  // underlying error
1916
                  while (chained != null)
1917
                  {
1918
                     if (chained instanceof Error)
1919
                     {
1920
                        isError = true;
1921
                     }
1922
                     
1923
                     if (chained instanceof StopConditionException)
1924
                     {
1925
                        isStop = true;
1926
                     }
1927
                     
1928
                     if (chained instanceof SilentUnwindException)
1929
                     {
1930
                        isStop = true;
1931
                     }
1932

    
1933
                     if (chained instanceof UnstoppableExitException)
1934
                     {
1935
                        throw new UnstoppableExitException(chained);
1936
                     }
1937
                     
1938
                     chained = chained.getCause();
1939
                  }
1940
                  
1941
                  // stop conditions can be generated via many (sometimes
1942
                  // unexpected) paths and may be wrapped in other exception
1943
                  // types, in such a case we don't need to log it
1944
                  if (!isStop)
1945
                  {
1946
                     // log the fact that we have an abnormal end occurring
1947
                     LOG.logp(Level.SEVERE,
1948
                              "StandardServer",
1949
                              "invoke",
1950
                              "Abnormal end!",
1951
                              th);
1952
                  }
1953
                  
1954
                  // treat Exception (but not Error) types the same as if
1955
                  // we got a STOP
1956
                  if (!isError)
1957
                  {
1958
                     if (stopDisp != 0)
1959
                     {
1960
                        if (stopDisp == 1)
1961
                           LogicalTerminal.setRelogin(true);
1962
            
1963
                        // in case of a SilentUnwindException, although we  
1964
                        // throw a STOP here, the TM is still aware of the
1965
                        // abnormal connection end and will overwrite the STOP
1966
                        // with another SilentUnwindException - which is OK 
1967
                        throw new StopConditionException(th);
1968
                     }
1969
                     
1970
                     TransactionManager.disableLoopProtection(null);
1971
                     TransactionManager.triggerRetry(null);
1972
                  }
1973
                  else
1974
                  {
1975
                     // the abend was an instance of Error - this is so
1976
                     // severe that we can't let the user continue 
1977
                  
1978
                     // set the flag to allow direct exit since we have such
1979
                     // a severe problem that we are going to exit to the
1980
                     // client BUT we MUST do this without trying to access
1981
                     // the client any further (via LogicalTerminal's
1982
                     // pauseBeforeEnd())
1983
                     TransactionManager.setProcessingQuit(true);
1984
                     
1985
                     // rethrow to ensure that we exit
1986
                     throw new RuntimeException(th);
1987
                  }
1988
               }
1989
               
1990
            }
1991
            while (TransactionManager.needsRetry());
1992
         }
1993
      }
1994
      
1995
      // let any other exception pass through to the caller!
1996
      
1997
      finally
1998
      {
1999
         // remove the topmost scope from the TransactionManager
2000
         TransactionManager.popScope();
2001
      }
2002
      
2003
      return result;
2004
   }
2005
   
2006
   /**
2007
    * Reads the portion of the directory where all preloaded application
2008
    * classes are listed and instantiates them.
2009
    * 
2010
    * @param    ds
2011
    *           instance of <code>DirectoryService</code> to read from
2012
    *
2013
    * @throws   ConfigurationException
2014
    *           when a class listed in the directory cannot be loaded
2015
    */
2016
   private static void loadStartup(DirectoryService ds)
2017
   throws ConfigurationException
2018
   {
2019
      String nodeId = Utils.findDirectoryNodePath(ds, "server-hooks", "strings", false);
2020
      
2021
      // check the node for existence and proper type 
2022
      if (nodeId == null)
2023
      {
2024
         return;
2025
      }
2026
      
2027
      // read all startup names 
2028
      String[] startups = ds.getNodeStrings(nodeId, "values");
2029
      
2030
      if (startups == null || startups.length == 0)
2031
      {
2032
         return;
2033
      }
2034
      
2035
      // check that only a single hook is assigned per jar
2036
      String[] loadedJars             = MultiClassLoader.listRegisteredJars();
2037
      Map<String, String> jarsToHooks = new HashMap<>();
2038
      if (loadedJars.length > 0)
2039
      {
2040
         for (String hookClass : startups)
2041
         {
2042
            for (String jar : loadedJars)
2043
            {
2044
               if (MultiClassLoader.handledByJar(hookClass, jar))
2045
               {
2046
                  if (jarsToHooks.containsKey(jar))
2047
                  {
2048
                    throw new ConfigurationException("Cannot load hook " +
2049
                          hookClass + " for jar " + jar + " because the hook " +
2050
                              jarsToHooks.get(jar) + " is already assigned " +
2051
                              " for this jar!");
2052
                  }
2053
                  
2054
                  jarsToHooks.put(jar, hookClass);
2055
                  break;
2056
               }
2057
            }
2058
         }
2059
      }
2060
      
2061
      // initialize startups
2062
      for (String hook : startups)
2063
      {
2064
         InitTermListener listener = null;
2065
         
2066
         try
2067
         {
2068
            listener = loadHook(hook, nodeId);
2069
         }
2070
         catch (RuntimeException rte)
2071
         {
2072
            LOG.logp(Level.SEVERE,
2073
                     "StandardServer.loadStartup",
2074
                     "",
2075
                     "failed to load " + hook + " with " + rte.toString());
2076
            
2077
            continue;
2078
         }
2079
         
2080
         serverHooks.add(listener);
2081
      }
2082
   }
2083
   
2084
   /**
2085
    * Initialize the list of jars from which to load application resources. This is read from
2086
    * the directory. If nothing is listed in the directory, only the {@code p2j.jar} file will
2087
    * be searched for resources. The {@code p2j.jar} file will be added to the end of this list,
2088
    * if not explicitly listed in the directory.
2089
    * 
2090
    * @param   ds
2091
    *          Instance of {@code DirectoryService} from which to read configuration.
2092
    */
2093
   private static void initResourceJarPaths(DirectoryService ds)
2094
   {
2095
      String nodeId = Utils.findDirectoryNodePath(ds, "resource-jars", "strings", false);
2096
      Set<String> jarSet = new LinkedHashSet<>();
2097
      
2098
      if (nodeId != null)
2099
      {
2100
         String[] jars = ds.getNodeStrings(nodeId, "values");
2101
         if (jars != null)
2102
         {
2103
            for (String jar : jars)
2104
            {
2105
               if (!jar.toLowerCase().endsWith(JAR_EXT))
2106
               {
2107
                  jar = jar + JAR_EXT;
2108
               }
2109
               jarSet.add(jar);
2110
            }
2111
         }
2112
      }
2113
      
2114
      // p2j.jar contains resources; ensure it is always the last resource jar, unless specified
2115
      // explicitly in the directory
2116
      jarSet.add(P2J_JAR);
2117
      
2118
      List<String> resPaths = new ArrayList<>();
2119
      String cp = System.getProperty("java.class.path");
2120
      String[] paths = cp.split(File.pathSeparator);
2121
      
2122
      // loop through the unqualified resource jar names
2123
      for (String jar : jarSet)
2124
      {
2125
         // loop through the classpath parts to find the paths associated with the resource jars
2126
         for (String path : paths)
2127
         {
2128
            if (path.endsWith(jar))
2129
            {
2130
               resPaths.add(path);
2131
               
2132
               break;
2133
            }
2134
         }
2135
      }
2136
      
2137
      resourceJarPaths = Collections.unmodifiableList(resPaths);
2138
   }
2139
   
2140
   /**
2141
    * Registers an entry point as an export.
2142
    * <p>
2143
    * The entry point method can be specified either by name or by name and 
2144
    * signature. The short form is acceptable when the method is unique 
2145
    * (not overloaded). Overloaded methods require signature specification to
2146
    * be fully resolved.
2147
    * 
2148
    * @param    group
2149
    *           export group name
2150
    * @param    entry
2151
    *           export entry name
2152
    * @param    methodSpec
2153
    *           class and method specification as in class.method(signature)
2154
    *           where the (signature) is optional.
2155
    *
2156
    * @return <code>true</code> if succeeded
2157
    */
2158
   private boolean exportMethod(String group, String entry, String methodSpec)
2159
   {
2160
      SecurityManager sm = SecurityManager.getInstance();
2161
      LOG.finer("registering " + group + ":" + entry + " as " + methodSpec);
2162

    
2163
      // parse the method specification
2164
      String className = null;
2165
      String methodName = null;
2166
      String signature = null;
2167
      int signInd = methodSpec.indexOf('(');
2168
      
2169
      if (signInd == -1)
2170
         methodName = methodSpec;
2171
      else
2172
      {
2173
         methodName = methodSpec.substring(0, signInd);
2174
         signature  = methodSpec.substring(signInd);
2175
      }
2176

    
2177
      signInd = methodName.lastIndexOf('.');
2178
      
2179
      if (signInd == -1)         // fully qualified name always has dots
2180
         return false;
2181

    
2182
      className = methodName.substring(0, signInd);
2183
      methodName = methodName.substring(signInd + 1);
2184

    
2185
      // inspecting the class
2186
      Class<?> appClass = null;
2187

    
2188
      try
2189
      {
2190
         // can not use Class.forName, as it caches the loaded class in 
2191
         // some native code from which it can never be removed
2192
         appClass = ClassLoader.getSystemClassLoader().loadClass(className);
2193
      }
2194
      catch (Exception e)
2195
      {
2196
         return false;
2197
      }
2198

    
2199
      // looking up the method
2200
      Method[] methods = appClass.getMethods();
2201
      Method method = null;
2202
      int mods = 0;
2203
      int count = 0;
2204

    
2205
      for (int i = 0; i < methods.length; i ++)
2206
      {
2207
         mods = methods[i].getModifiers();
2208
         if (!Modifier.isPublic(mods))
2209
            continue;
2210
         if (methods[i].getName().equals(methodName))
2211
         {
2212
            count ++;
2213
            if (signature != null)
2214
            {
2215
               if (getSignature(methods[i]).equals(signature))
2216
               {
2217
                  method = methods[i];
2218
                  break;
2219
               }
2220
            }
2221
            else
2222
               method = methods[i];
2223
         }
2224
      }
2225

    
2226
      if (method == null)                    // no such public method
2227
         return false;
2228
      if (signature == null && count > 1)    // ambiguous method name
2229
         return false;
2230

    
2231
      // static or instance?
2232
      mods = method.getModifiers();
2233
      Object ref = null;
2234
      if (!Modifier.isStatic(mods))
2235
      {
2236
         // instance method. check if already instantiated the class
2237
         if (!inst.containsKey(className))
2238
         {
2239
            try
2240
            {
2241
               ref = appClass.getDeclaredConstructor().newInstance();
2242
            }
2243
            catch (Exception e)
2244
            {
2245
               return false;
2246
            }
2247
            inst.put(className, ref);
2248
         }
2249
         else
2250
            ref = inst.get(className);
2251
      }
2252

    
2253
      // finally, do the registration
2254
      RoutingKey key = Dispatcher.generateRoutingKey(group, entry);
2255
      
2256
      if (key == null)
2257
         return false;
2258
      
2259
      try
2260
      {
2261
         Dispatcher.addMethod(key, ref, method, null);
2262
      }
2263
      catch (Exception e)
2264
      {
2265
         return false;
2266
      }
2267

    
2268
      return true;
2269
   }
2270

    
2271
   /**
2272
    * Returns method's signature as text.
2273
    * 
2274
    * @param    method
2275
    *           <code>Method</code> to get the signature for.
2276
    * 
2277
    * @return   The method signature in parenthesis.
2278
    */
2279
   private String getSignature(Method method)
2280
   {
2281
      Class<?>[] pars = null;
2282
      StringBuilder sb = new StringBuilder();
2283
      int i = 0;
2284
      
2285
      pars = method.getParameterTypes();
2286
      sb.append("(");
2287
      for (i = 0; i < pars.length; i ++)
2288
      {
2289
         sb.append(pars[i].getName());
2290
         sb.append(",");
2291
      }
2292
      if (i > 0)
2293
         sb.setLength(sb.length() - 1);
2294
      sb.append(")");
2295
      
2296
      return new String(sb);
2297
   }
2298
   
2299
   /**
2300
    * Notify server hook about application initialization.
2301
    * 
2302
    * @throws  ConfigurationException
2303
    *          If {@code initialize()} method any of {@code serverHooks} fails.
2304
    */
2305
   private void hookInitialize() 
2306
   throws ConfigurationException
2307
   {
2308
      for (InitTermListener hook : serverHooks)
2309
      {
2310
         try
2311
         {
2312
            hook.initialize();
2313
         }
2314
         catch (Throwable t)
2315
         {
2316
            throw new ConfigurationException(" Initialization failure", t);
2317
         }
2318
      }
2319
   }
2320
   
2321
   /**
2322
    * Notify server hook about application termination.
2323
    * 
2324
    * @param    t
2325
    *           Parameter which will be passed to
2326
    *           {@link InitTermListener#terminate(Throwable)}.
2327
    */
2328
   private void hookTerminate(Throwable t)
2329
   {
2330
      synchronized(shutdownLock)
2331
      {
2332
         if (!shutdownDone)
2333
         {
2334
            ListIterator<InitTermListener> iterator = 
2335
               serverHooks.listIterator(serverHooks.size());
2336
            
2337
            while (iterator.hasPrevious())
2338
            {
2339
               try
2340
               {
2341
                  iterator.previous().terminate(t);
2342
               }
2343
               catch (Throwable e)
2344
               {
2345
                  LOG.logp(Level.SEVERE,
2346
                           "StandardServer",
2347
                           "hookTerminate",
2348
                           "terminate() failed!",
2349
                           e);
2350
               }
2351
            }
2352
            
2353
            shutdownDone = true;
2354
         }
2355
      }
2356
   }
2357
   
2358
   /**
2359
    * Abstract adapter for a basic init/term listener.
2360
    */
2361
   static abstract class AbstractInitTermListener
2362
   implements InitTermListener
2363
   {
2364
      /**
2365
       * Called when the server initializes.
2366
       */
2367
      @Override
2368
      abstract public void initialize();
2369

    
2370
      /**
2371
       * Called when the server terminates. This routine does nothing.
2372
       * 
2373
       * @param    t
2374
       *           If not <code>null</code>, this describes the reason for the server's exit.
2375
       */
2376
      @Override
2377
      public void terminate(Throwable t)
2378
      {
2379
         // do nothing
2380
      }
2381
   }
2382
   
2383
   /**
2384
    * Executes the exported entry point as a legacy external program.
2385
    */
2386
   private static class LegacyInvoker
2387
   implements Isolatable
2388
   {
2389
      /** The external program name. */
2390
      String programName;
2391

    
2392
      /**
2393
       * Initialize the invoker to execute the given program name.
2394
       */
2395
      public LegacyInvoker(String programName)
2396
      {
2397
         this.programName = programName;
2398
      }
2399
      
2400
      /**
2401
       * Execute the {@link #programName external program}, by emulating a RUN statement. 
2402
       */
2403
      @Override
2404
      public Object execute() 
2405
      throws ReflectiveOperationException
2406
      {
2407
         // reset the user's session-specific elapsed millis counter
2408
         date.elapsed(true);
2409
   
2410
         // password aging support
2411
         SecurityManager sm = SecurityManager.getInstance();
2412
         
2413
         if (sm.isPasswordAged())
2414
            sm.changePassword();
2415

    
2416
         InvokeConfig cfg = new InvokeConfig(programName);
2417
         return ControlFlowOps.invoke(cfg);
2418
      }
2419
   }
2420
   
2421
   /**
2422
    * Prepares the exported entry point to be called in the properly
2423
    * initialized transactionable environment.
2424
    */
2425
   private static class MainInvoker
2426
   implements Isolatable
2427
   {
2428
      /** Class for the main entry point */
2429
      Class<?> cl = null;
2430
      
2431
      /** Entry point method */
2432
      Method entry = null;
2433
      
2434
      /** stop disposition value */
2435
      @SuppressWarnings("unused")
2436
      int stopDisp = -1;
2437
      
2438
      /**
2439
       * Constructor that simply remembers the parameters of the planned
2440
       * entry point invocation.
2441
       *
2442
       * @param    cls
2443
       *           The class where the called method is defined.
2444
       * @param    method
2445
       *           The method to call.
2446
       * @param    stop 
2447
       *           The stop condition disposition value.
2448
       */
2449
      private MainInvoker(Class<?> cls, Method method, int stop)
2450
      {
2451
         cl = cls;
2452
         entry = method;
2453
         stopDisp = stop;
2454
      }
2455
      
2456
      /**
2457
       * Specifies the method that is executed in a transaction environment, 
2458
       *  
2459
       * @return the result of the execution or <code>null</code> if void.
2460
       */
2461
      public Object execute()
2462
      throws ReflectiveOperationException
2463
      {
2464
         // reset the user's session-specific elapsed millis counter
2465
         date.elapsed(true);
2466
   
2467
         // password aging support
2468
         SecurityManager sm = SecurityManager.getInstance();
2469
         
2470
         if (sm.isPasswordAged())
2471
            sm.changePassword();
2472
   
2473
         // calling the main entry
2474
         return Utils.invoke(cl, entry, (Object[])null);
2475
      }   
2476
   }
2477
}