/
BrokerPool.java
2173 lines (1876 loc) · 81.3 KB
/
BrokerPool.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* eXist Open Source Native XML Database
* Copyright (C) 2003-2013 The eXist-db Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.storage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.NumberFormat;
import java.util.*;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.exist.Database;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.CollectionCache;
import org.exist.collections.CollectionConfiguration;
import org.exist.collections.CollectionConfigurationManager;
import org.exist.collections.triggers.*;
import org.exist.config.ConfigurationDocumentTrigger;
import org.exist.config.Configurator;
import org.exist.config.annotation.ConfigurationClass;
import org.exist.config.annotation.ConfigurationFieldAsAttribute;
import org.exist.debuggee.Debuggee;
import org.exist.debuggee.DebuggeeFactory;
import org.exist.dom.SymbolTable;
import org.exist.indexing.IndexManager;
import org.exist.management.AgentFactory;
import org.exist.numbering.DLNFactory;
import org.exist.numbering.NodeIdFactory;
import org.exist.plugin.PluginsManager;
import org.exist.plugin.PluginsManagerImpl;
import org.exist.repo.ClasspathHelper;
import org.exist.repo.ExistRepository;
import org.exist.scheduler.Scheduler;
import org.exist.scheduler.impl.QuartzSchedulerImpl;
import org.exist.scheduler.impl.SystemTaskJobImpl;
import org.exist.security.AuthenticationException;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.SecurityManager;
import org.exist.security.Subject;
import org.exist.security.internal.SecurityManagerImpl;
import org.exist.storage.btree.DBException;
import org.exist.storage.lock.DeadlockDetection;
import org.exist.storage.lock.FileLock;
import org.exist.storage.lock.Lock;
import org.exist.storage.lock.ReentrantReadWriteLock;
import org.exist.storage.sync.Sync;
import org.exist.storage.sync.SyncTask;
import org.exist.storage.txn.TransactionException;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.*;
import org.exist.util.Configuration.StartupTriggerConfig;
import org.exist.util.hashtable.MapRWLock;
import org.exist.util.hashtable.MapRWLock.LongOperation;
import org.exist.xmldb.ShutdownListener;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.PerformanceStats;
import org.expath.pkg.repo.PackageException;
/**
* This class controls all available instances of the database.
* Use it to configure, start and stop database instances.
* You may have multiple instances defined, each using its own configuration.
* To define multiple instances, pass an identification string to {@link #configure(String, int, int, Configuration)}
* and use {@link #getInstance(String)} to retrieve an instance.
*
*@author Wolfgang Meier <wolfgang@exist-db.org>
*@author Pierrick Brihaye <pierrick.brihaye@free.fr>
*/
//TODO : in the future, separate the design between the Map of DBInstances and their non static implementation
@ConfigurationClass("pool")
public class BrokerPool implements Database {
private final static Logger LOG = Logger.getLogger(BrokerPool.class);
private final static TreeMap<String, BrokerPool> instances = new TreeMap<String, BrokerPool>();
private final static Map<String, Throwable> instancesInitializtionException = new TreeMap<String, Throwable>();
//on-start, ready, go
/*** initializing subcomponents */
public final static String SIGNAL_STARTUP = "startup";
/*** ready for recovery & read-only operations */
public final static String SIGNAL_READINESS = "ready";
/*** ready for writable operations */
public final static String SIGNAL_WRITABLE = "writable";
/*** ready for writable operations */
public final static String SIGNAL_STARTED = "started";
/*** running shutdown sequence */
public final static String SIGNAL_SHUTDOWN = "shutdown";
/*** recovery aborted, db stopped */
public final static String SIGNAL_ABORTED = "aborted";
/**
* The name of a default database instance for those who are too lazy to provide parameters ;-).
*/
public final static String DEFAULT_INSTANCE_NAME = "exist";
public static final String CONFIGURATION_CONNECTION_ELEMENT_NAME = "db-connection";
public static final String CONFIGURATION_STARTUP_ELEMENT_NAME = "startup";
public static final String CONFIGURATION_POOL_ELEMENT_NAME = "pool";
public static final String CONFIGURATION_SECURITY_ELEMENT_NAME = "security";
public static final String CONFIGURATION_RECOVERY_ELEMENT_NAME = "recovery";
public static final String DISK_SPACE_MIN_ATTRIBUTE = "minDiskSpace";
public static final String DATA_DIR_ATTRIBUTE = "files";
//TODO : move elsewhere ?
public final static String RECOVERY_ENABLED_ATTRIBUTE = "enabled";
public final static String RECOVERY_POST_RECOVERY_CHECK = "consistency-check";
//TODO : move elsewhere ?
public final static String COLLECTION_CACHE_SIZE_ATTRIBUTE = "collectionCacheSize";
public final static String MIN_CONNECTIONS_ATTRIBUTE = "min";
public final static String MAX_CONNECTIONS_ATTRIBUTE = "max";
public final static String SYNC_PERIOD_ATTRIBUTE = "sync-period";
public final static String SHUTDOWN_DELAY_ATTRIBUTE = "wait-before-shutdown";
public final static String NODES_BUFFER_ATTRIBUTE = "nodesBuffer";
//Various configuration property keys (set by the configuration manager)
public final static String PROPERTY_STARTUP_TRIGGERS = "startup.triggers";
public final static String PROPERTY_DATA_DIR = "db-connection.data-dir";
public final static String PROPERTY_MIN_CONNECTIONS = "db-connection.pool.min";
public final static String PROPERTY_MAX_CONNECTIONS = "db-connection.pool.max";
public final static String PROPERTY_SYNC_PERIOD = "db-connection.pool.sync-period";
public final static String PROPERTY_SHUTDOWN_DELAY = "wait-before-shutdown";
public static final String DISK_SPACE_MIN_PROPERTY = "db-connection.diskSpaceMin";
//TODO : move elsewhere ?
public final static String PROPERTY_COLLECTION_CACHE_SIZE = "db-connection.collection-cache-size";
//TODO : move elsewhere ? Get fully qualified class name ?
public final static String DEFAULT_SECURITY_CLASS = "org.exist.security.internal.SecurityManagerImpl";
public final static String PROPERTY_SECURITY_CLASS = "db-connection.security.class";
public final static String PROPERTY_RECOVERY_ENABLED = "db-connection.recovery.enabled";
public final static String PROPERTY_RECOVERY_CHECK = "db-connection.recovery.consistency-check";
public final static String PROPERTY_SYSTEM_TASK_CONFIG = "db-connection.system-task-config";
public static final String PROPERTY_NODES_BUFFER = "db-connection.nodes-buffer";
public static final String PROPERTY_EXPORT_ONLY = "db-connection.emergency";
public static final String DOC_ID_MODE_ATTRIBUTE = "doc-ids";
public static final String DOC_ID_MODE_PROPERTY = "db-connection.doc-ids.mode";
//TODO : inline the class ? or... make it configurable ?
// WM: inline. I don't think users need to be able to overwrite this.
// They can register their own shutdown hooks any time.
private final static Thread shutdownHook = new Thread() {
/**
* Make sure that all instances are cleanly shut down.
*/
@Override
public void run() {
LOG.info("Executing shutdown thread");
BrokerPool.stopAll(true);
}
};
//TODO : make this defaut value configurable ? useless if we have a registerShutdownHook(Thread aThread) method (null = deregister)
private static boolean registerShutdownHook = true;
private static Observer statusObserver = null;
private StatusReporter statusReporter = null;
/**
* Whether of not the JVM should run the shutdown thread.
* @param register <code>true</code> if the JVM should run the thread
*/
//TODO : rename as activateShutdownHook ? or registerShutdownHook(Thread aThread)
// WM: it is probably not necessary to allow users to register their own hook. This method
// is only used once, by class org.exist.jetty.JettyStart, which registers its own hook.
public final static void setRegisterShutdownHook(boolean register) {
/*
* TODO : call Runtime.getRuntime().removeShutdownHook or Runtime.getRuntime().registerShutdownHook
* depending of the value of register
* Since Java doesn't provide a convenient way to know if a shutdown hook has been registrered,
* we may have to catch IllegalArgumentException
*/
//TODO : check that the JVM is not shutting down
registerShutdownHook = register;
}
//TODO : make it non-static since every database instance may have its own policy.
//TODO : make a defaut value that could be overwritten by the configuration
// WM: this is only used by junit tests to test the recovery process.
/**
* For testing only: triggers a database corruption by disabling the page caches. The effect is
* similar to a sudden power loss or the jvm being killed. The flag is used by some
* junit tests to test the recovery process.
*/
public static boolean FORCE_CORRUPTION = false;
/**
* Creates and configures a default database instance and adds it to the pool.
* Call this before calling {link #getInstance()}.
* If a default database instance already exists, the new configuration is ignored.
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
* @throws EXistException
*@exception EXistException If the initialization fails.
*/
//TODO : in the future, we should implement a Configurable interface
public final static void configure(int minBrokers, int maxBrokers, Configuration config)
throws EXistException, DatabaseConfigurationException {
configure(DEFAULT_INSTANCE_NAME, minBrokers, maxBrokers, config);
}
/**
* Creates and configures a database instance and adds it to the pool.
* Call this before calling {link #getInstance()}.
* If a database instance with the same name already exists, the new configuration is ignored.
* @param instanceName A <strong>unique</strong> name for the database instance.
* It is possible to have more than one database instance (with different configurations for example).
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
* @throws EXistException If the initialization fails.
*/
//TODO : in the future, we should implement a Configurable interface
public final static void configure(
String instanceName, int minBrokers, int maxBrokers,
Configuration config) throws EXistException {
//Check if there is a database instance in the pool with the same id
BrokerPool instance = instances.get(instanceName);
if (instance == null) {
LOG.debug("configuring database instance '" + instanceName + "'...");
try {
//Create the instance
instance = new BrokerPool(instanceName, minBrokers, maxBrokers, config);
//Add it to the pool
instances.put(instanceName, instance);
//We now have at least an instance...
if(instances.size() == 1) {
//... so a ShutdownHook may be interesting
if(registerShutdownHook) {
try {
//... currently an eXist-specific one. TODO : make it configurable ?
Runtime.getRuntime().addShutdownHook(shutdownHook);
LOG.debug("shutdown hook registered");
} catch(final IllegalArgumentException e) {
LOG.warn("shutdown hook already registered");
}
}
}
} catch (final Throwable ex){
// Catch all possible issues and report.
LOG.error("Unable to initialize database instance '" + instanceName
+ "': "+ex.getMessage(), ex);
instancesInitializtionException.put(instanceName, ex);
// TODO: Add throw of exception? DW
}
//TODO : throw an exception here rather than silently ignore an *explicit* parameter ?
// WM: maybe throw an exception. Users can check if a db is already configured.
} else
{LOG.warn("database instance '" + instanceName + "' is already configured");}
}
/** Returns whether or not the default database instance is configured.
* @return <code>true</code> if it is configured
*/
//TODO : in the future, we should implement a Configurable interface
public final static boolean isConfigured() {
return isConfigured(DEFAULT_INSTANCE_NAME);
}
/** Returns whether or not a database instance is configured.
* @param id The name of the database instance
* @return <code>true</code> if it is configured
*/
//TODO : in the future, we should implement a Configurable interface
public final static boolean isConfigured(String id) {
//Check if there is a database instance in the pool with the same id
final BrokerPool instance = instances.get(id);
//No : it *can't* be configured
if (instance == null)
{return false;}
//Yes : it *may* be configured
return instance.isInstanceConfigured();
}
/**Returns a broker pool for the default database instance.
* @return The broker pool
* @throws EXistException If the database instance is not available (not created, stopped or not configured)
*/
public final static BrokerPool getInstance() throws EXistException {
return getInstance(DEFAULT_INSTANCE_NAME);
}
/**Returns a broker pool for a database instance.
* @param instanceName The name of the database instance
* @return The broker pool
* @throws EXistException If the instance is not available (not created, stopped or not configured)
*/
public final static BrokerPool getInstance(String instanceName) throws EXistException {
//Check if there is a database instance in the pool with the same id
final BrokerPool instance = instances.get(instanceName);
if (instance != null)
//TODO : call isConfigured(id) and throw an EXistException if relevant ?
{return instance;}
final Throwable exception = instancesInitializtionException.get(instanceName);
if (exception != null) {
if (exception instanceof EXistException)
{throw (EXistException)exception;}
throw new EXistException(exception);
}
throw new EXistException("database instance '" + instanceName + "' is not available");
}
/** Returns an iterator over the database instances.
* @return The iterator
*/
public final static Iterator<BrokerPool> getInstances() {
return instances.values().iterator();
}
public final static boolean isInstancesEmpty() {
return instances.values().isEmpty();
}
/** Stops the default database instance. After calling this method, it is
* no longer configured.
* @throws EXistException If the default database instance is not available (not created, stopped or not configured)
*/
public final static void stop() throws EXistException {
stop(DEFAULT_INSTANCE_NAME);
}
/** Stops the given database instance. After calling this method, it is
* no longer configured.
* @param id The name of the database instance
* @throws EXistException If the database instance is not available (not created, stopped or not configured)
*/
public final static void stop(String id) throws EXistException {
final BrokerPool instance = getInstance(id);
instance.shutdown();
}
/** Stops all the database instances. After calling this method, the database instances are
* no longer configured.
* @param killed <code>true</code> when invoked by an exiting JVM
*/
public final static void stopAll(boolean killed) {
//Create a temporary vector
final Vector<BrokerPool> tmpInstances = new Vector<BrokerPool>();
for (final BrokerPool instance : instances.values()) {
//and feed it with the living database instances
tmpInstances.add(instance);
}
//Iterate over the living database instances
for (final BrokerPool instance : tmpInstances) {
if (instance.conf != null)
//Shut them down
{instance.shutdown(killed);}
}
//Clear the living instances container : they are all sentenced to death...
instances.clear();
}
public final static void systemInfo() {
for (final BrokerPool instance : instances.values()) {
instance.printSystemInfo();
}
}
public static void registerStatusObserver(Observer observer) {
statusObserver = observer;
LOG.debug("registering observer: " + observer.getClass().getName());
}
/* END OF STATIC IMPLEMENTATION */
/**
* Default values
*/
//TODO : make them static when we have 2 classes
private final int DEFAULT_MIN_BROKERS = 1;
private final int DEFAULT_MAX_BROKERS = 15;
public final long DEFAULT_SYNCH_PERIOD = 120000;
public final long DEFAULT_MAX_SHUTDOWN_WAIT = 45000;
//TODO : move this default setting to org.exist.collections.CollectionCache ?
public final int DEFAULT_COLLECTION_BUFFER_SIZE = 64;
public static final String PROPERTY_PAGE_SIZE = "db-connection.page-size";
public static final int DEFAULT_PAGE_SIZE = 4096;
/**
* <code>true</code> if the database instance is able to handle transactions.
*/
private boolean transactionsEnabled;
/**
* The name of the database instance
*/
private String instanceName;
//TODO: change 0 = initializing, 1 = operating, -1 = shutdown (shabanovd)
private final static int SHUTDOWN = -1;
private final static int INITIALIZING = 0;
private final static int OPERATING = 1;
// volatile so this doesn't get optimized away or into a CPU register in some thread
private volatile int status = INITIALIZING;
/**
* The number of brokers for the database instance
*/
private int brokersCount = 0;
/**
* The minimal number of brokers for the database instance
*/
@ConfigurationFieldAsAttribute("min")
private int minBrokers;
/**
* The maximal number of brokers for the database instance
*/
@ConfigurationFieldAsAttribute("max")
private int maxBrokers;
/**
* The number of inactive brokers for the database instance
*/
private Stack<DBBroker> inactiveBrokers = new Stack<DBBroker>();
/**
* The number of active brokers for the database instance
*/
private MapRWLock<Thread, DBBroker> activeBrokers = new MapRWLock<Thread, DBBroker>( new IdentityHashMap<Thread, DBBroker>() );
/**
* The configuration object for the database instance
*/
protected Configuration conf = null;
/**
* <code>true</code> if a cache synchronization event is scheduled
*/
//TODO : rename as syncScheduled ?
//TODO : alternatively, delete this member and create a Sync.NOSYNC event
private boolean syncRequired = false;
/**
* The kind of scheduled cache synchronization event.
* One of {@link org.exist.storage.sync.Sync#MAJOR_SYNC} or {@link org.exist.storage.sync.Sync#MINOR_SYNC}
*/
private int syncEvent = 0;
private boolean checkpoint = false;
/**
* <code>true</code> if the database instance is running in read-only mode.
*/
//TODO : this should be computed by the DBrokers depending of their configuration/capabilities
//TODO : for now, this member is used for recovery management
private boolean isReadOnly;
@ConfigurationFieldAsAttribute("pageSize")
private int pageSize;
private FileLock dataLock;
/**
* The transaction manager of the database instance.
*/
private TransactionManager transactionManager = null;
/**
* Delay (in ms) for running jobs to return when the database instance shuts down.
*/
@ConfigurationFieldAsAttribute("wait-before-shutdown")
private long maxShutdownWait;
/**
* The scheduler for the database instance.
*/
@ConfigurationFieldAsAttribute("scheduler")
private Scheduler scheduler;
/**
* Manages pluggable index structures.
*/
private IndexManager indexManager;
/**
* Global symbol table used to encode element and attribute qnames.
*/
private SymbolTable symbols;
/**
* Cache synchronization on the database instance.
*/
@ConfigurationFieldAsAttribute("sync-period")
private long majorSyncPeriod = DEFAULT_SYNCH_PERIOD; //the period after which a major sync should occur
private long lastMajorSync = System.currentTimeMillis(); //time the last major sync occurred
/**
* The listener that is notified when the database instance shuts down.
*/
private ShutdownListener shutdownListener = null;
/**
* The security manager of the database instance.
*/
private SecurityManager securityManager = null;
/**
* The plugin manager.
*/
private PluginsManagerImpl pluginManager = null;
/**
* The global notification service used to subscribe
* to document updates.
*/
private NotificationService notificationService = null;
private long nextSystemStatus = System.currentTimeMillis();
/**
* The cache in which the database instance may store items.
*/
private DefaultCacheManager cacheManager;
private CollectionCacheManager collectionCacheMgr;
private long reservedMem;
/**
* The pool in which the database instance's <strong>compiled</strong> XQueries are stored.
*/
private XQueryPool xQueryPool;
/**
* The monitor in which the database instance's strong>running</strong> XQueries are managed.
*/
private ProcessMonitor processMonitor;
/**
* Global performance stats to gather function execution statistics
* from all queries running on this database instance.
*/
private PerformanceStats xqueryStats;
/**
* The global manager for accessing collection configuration files from the database instance.
*/
private CollectionConfigurationManager collectionConfigurationManager = null;
/**
* The cache in which the database instance's collections are stored.
*/
//TODO : rename as collectionsCache ?
protected CollectionCache collectionCache;
/**
* The pool in which the database instance's readers are stored.
*/
protected XMLReaderPool xmlReaderPool;
private NodeIdFactory nodeFactory = new DLNFactory();
//TODO : is another value possible ? If no, make it static
// WM: no, we need one lock per database instance. Otherwise we would lock another database.
private Lock globalXUpdateLock = new ReentrantReadWriteLock("xupdate");
private Subject serviceModeUser = null;
private boolean inServiceMode = false;
//the time that the database was started
private final Calendar startupTime = Calendar.getInstance();
private BrokerWatchdog watchdog = null;
private ClassLoader classLoader;
private ExistRepository expathRepo = null;
/** Creates and configures the database instance.
* @param instanceName A name for the database instance.
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param conf The configuration object for the database instance
* @throws EXistException If the initialization fails.
*/
//TODO : Then write a configure(int minBrokers, int maxBrokers, Configuration conf) method
private BrokerPool(String instanceName, int minBrokers, int maxBrokers, Configuration conf)
throws EXistException, DatabaseConfigurationException {
Integer anInteger;
Long aLong;
Boolean aBoolean;
final NumberFormat nf = NumberFormat.getNumberInstance();
this.classLoader = Thread.currentThread().getContextClassLoader();
//TODO : ensure that the instance name is unique ?
//WM: needs to be done in the configure method.
this.instanceName = instanceName;
//TODO : find a nice way to (re)set the default values
//TODO : create static final members for configuration keys
this.minBrokers = DEFAULT_MIN_BROKERS;
this.maxBrokers = DEFAULT_MAX_BROKERS;
this.maxShutdownWait = DEFAULT_MAX_SHUTDOWN_WAIT;
//TODO : read from configuration
this.transactionsEnabled = true;
this.minBrokers = minBrokers;
this.maxBrokers = maxBrokers;
/*
* strange enough, the settings provided by the constructor may be overriden
* by the ones *explicitely* provided by the constructor
* TODO : consider a private constructor BrokerPool(String instanceName) then configure(int minBrokers, int maxBrokers, Configuration config)
*/
anInteger = (Integer) conf.getProperty(PROPERTY_MIN_CONNECTIONS);
if (anInteger != null)
{this.minBrokers = anInteger.intValue();}
anInteger = (Integer) conf.getProperty(PROPERTY_MAX_CONNECTIONS);
if (anInteger != null)
{this.maxBrokers = anInteger.intValue();}
//TODO : sanity check : minBrokers shall be lesser than or equal to maxBrokers
//TODO : sanity check : minBrokers shall be positive
LOG.info("database instance '" + instanceName + "' will have between " + nf.format(this.minBrokers) + " and " + nf.format(this.maxBrokers) + " brokers");
//TODO : use the periodicity of a SystemTask (see below)
aLong = (Long) conf.getProperty(PROPERTY_SYNC_PERIOD);
if (aLong != null)
/*this.*/{majorSyncPeriod = aLong.longValue();}
//TODO : sanity check : the synch period should be reasonable
LOG.info("database instance '" + instanceName + "' will be synchronized every " + nf.format(/*this.*/majorSyncPeriod) + " ms");
aLong = (Long) conf.getProperty(BrokerPool.PROPERTY_SHUTDOWN_DELAY);
if (aLong != null) {
this.maxShutdownWait = aLong.longValue();
}
//TODO : sanity check : the shutdown period should be reasonable
LOG.info("database instance '" + instanceName + "' will wait " + nf.format(this.maxShutdownWait) + " ms during shutdown");
aBoolean = (Boolean) conf.getProperty(PROPERTY_RECOVERY_ENABLED);
if (aBoolean != null) {
this.transactionsEnabled = aBoolean.booleanValue();
}
LOG.info("database instance '" + instanceName + "' is enabled for transactions : " + this.transactionsEnabled);
pageSize = conf.getInteger(PROPERTY_PAGE_SIZE);
if (pageSize < 0)
{pageSize = DEFAULT_PAGE_SIZE;}
/* TODO: start -adam- remove OLD SystemTask initialization */
//How ugly : needs refactoring...
/* Configuration.SystemTaskConfig systemTasksConfigs[] = (Configuration.SystemTaskConfig[]) conf.getProperty(BrokerPool.PROPERTY_SYSTEM_TASK_CONFIG);
if (systemTasksConfigs != null) {
for (int i = 0; i < systemTasksConfigs.length; i++) {
try {
Class clazz = Class.forName(systemTasksConfigs[i].getClassName());
SystemTask task = (SystemTask) clazz.newInstance();
if (!(task instanceof SystemTask))
//TODO : shall we ignore the exception ?
throw new EXistException("'" + task.getClass().getName() + "' is not an instance of org.exist.storage.SystemTask");
task.configure(conf, systemTasksConfigs[i].getProperties());
systemTasks.add(task);
//TODO : remove when SystemTask has a getPeriodicity() method
systemTasksPeriods.add(systemTasksConfigs[i]);
LOG.info("added system task instance '" + task.getClass().getName() + "' to be executed every " + nf.format(systemTasksConfigs[i].getPeriod()) + " ms");
}
catch (ClassNotFoundException e) {
//TODO : shall we ignore the exception ?
throw new EXistException("system task class '" + systemTasksConfigs[i].getClassName() + "' not found");
}
catch (InstantiationException e) {
//TODO : shall we ignore the exception ?
throw new EXistException("system task '" + systemTasksConfigs[i].getClassName() + "' can not be instantiated");
}
catch (IllegalAccessException e) {
//TODO : shall we ignore the exception ?
throw new EXistException("system task '" + systemTasksConfigs[i].getClassName() + "' can not be accessed");
}
}
//TODO : why not add a default Sync task here if there is no instanceof Sync in systemTasks ?
}
*/
/* TODO: end -adam- remove OLD SystemTask initialization */
//TODO : move this to initialize ? (cant as we need it for FileLockHeartBeat)
scheduler = new QuartzSchedulerImpl(this, conf);
//TODO : since we need one :-( (see above)
this.isReadOnly = !canReadDataDir(conf);
LOG.debug("isReadOnly: " + isReadOnly);
//Configuration is valid, save it
this.conf = conf;
//TODO : in the future, we should implement an Initializable interface
try {
initialize();
} catch (final Throwable e) {
// remove that file lock we may have acquired in canReadDataDir
if (dataLock != null && !isReadOnly)
dataLock.release();
if (!instances.containsKey(instanceName))
{instancesInitializtionException.put(instanceName, e);}
if (e instanceof EXistException)
{throw (EXistException) e;}
if (e instanceof DatabaseConfigurationException)
{throw (DatabaseConfigurationException) e;}
throw new EXistException(e);
}
//TODO : move this to initialize ?
//setup database synchronization job
if (majorSyncPeriod > 0) {
//TODO : why not automatically register Sync in system tasks ?
// scheduler.createPeriodicJob(2500, new Sync(), 2500);
final SyncTask syncTask = new SyncTask();
syncTask.configure(conf, null);
scheduler.createPeriodicJob(2500, new SystemTaskJobImpl(SyncTask.getJobName(), syncTask), 2500);
}
if ("yes".equals(System.getProperty("trace.brokers", "no")))
{watchdog = new BrokerWatchdog();}
}
//TODO : create a canReadJournalDir() method in the *relevant* class. The two directories may be different.
protected boolean canReadDataDir(Configuration conf) throws EXistException {
String dataDir = (String) conf.getProperty(PROPERTY_DATA_DIR);
if (dataDir == null)
{dataDir = "data";} //TODO : DEFAULT_DATA_DIR
final File dir = new File(dataDir);
if (!dir.exists()) {
try {
//TODO : shall we force the creation ? use a parameter to decide ?
LOG.info("Data directory '" + dir.getAbsolutePath() + "' does not exist. Creating one ...");
dir.mkdirs();
} catch (final SecurityException e) {
LOG.info("Cannot create data directory '" + dir.getAbsolutePath() + "'. Switching to read-only mode.");
return false;
}
}
//Save it for further use.
//TODO : "data-dir" has sense for *native* brokers
conf.setProperty(PROPERTY_DATA_DIR, dataDir);
if (!dir.canWrite()) {
LOG.info("Cannot write to data directory: " + dir.getAbsolutePath() + ". Switching to read-only mode.");
return false;
}
// try to acquire lock on the data dir
dataLock = new FileLock(this, dir, "dbx_dir.lck");
try {
boolean locked = dataLock.tryLock();
if (!locked) {
throw new EXistException("The database directory seems to be locked by another " +
"database instance. Found a valid lock file: " + dataLock.getFile());
}
} catch (final ReadOnlyException e) {
LOG.info(e.getMessage() + ". Switching to read-only mode!!!");
return false;
}
return true;
}
/**
* Initializes the database instance.
* @throws EXistException
*/
protected void initialize() throws EXistException, DatabaseConfigurationException {
if(LOG.isDebugEnabled()) {
LOG.debug("initializing database instance '" + instanceName + "'...");
}
//Flag to indicate that we are initializing
status = INITIALIZING;
// Don't allow two threads to do a race on this. May be irrelevant as this is only called
// from the constructor right now.
synchronized (this) {
try {
statusReporter = new StatusReporter(SIGNAL_STARTUP);
if (statusObserver != null) {
statusReporter.addObserver(statusObserver);
}
Thread statusThread = new Thread(statusReporter);
statusThread.start();
// statusReporter may have to be terminated or the thread can/will hang.
try {
final boolean exportOnly = (Boolean) conf.getProperty(PROPERTY_EXPORT_ONLY, false);
//create the security manager
securityManager = new SecurityManagerImpl(this);
//REFACTOR : construct then configure
cacheManager = new DefaultCacheManager(this);
//REFACTOR : construct then configure
xQueryPool = new XQueryPool(conf);
//REFACTOR : construct then... configure
processMonitor = new ProcessMonitor(maxShutdownWait);
xqueryStats = new PerformanceStats(this);
//REFACTOR : construct then... configure
xmlReaderPool = new XMLReaderPool(conf, new XMLReaderObjectFactory(this), 5, 0);
//REFACTOR : construct then... configure
int bufferSize = conf.getInteger(PROPERTY_COLLECTION_CACHE_SIZE);
if(bufferSize == -1) {
bufferSize = DEFAULT_COLLECTION_BUFFER_SIZE;
}
collectionCache = new CollectionCache(this, bufferSize, 0.0001);
collectionCacheMgr = new CollectionCacheManager(this, collectionCache);
// compute how much memory should be reserved for caches to grow
final Runtime rt = Runtime.getRuntime();
final long maxMem = rt.maxMemory();
final long minFree = maxMem / 5;
reservedMem = cacheManager.getTotalMem() + collectionCacheMgr.getMaxTotal() + minFree;
LOG.debug("Reserved memory: " + reservedMem + "; max: " + maxMem + "; min: " + minFree);
notificationService = new NotificationService();
//REFACTOR : construct then... configure
//TODO : journal directory *may* be different from BrokerPool.PROPERTY_DATA_DIR
transactionManager = new TransactionManager(this, new File((String) conf.getProperty(BrokerPool.PROPERTY_DATA_DIR)), isTransactional());
try {
transactionManager.initialize();
} catch (final ReadOnlyException e) {
LOG.warn(e.getMessage() + ". Switching to read-only mode!!!");
isReadOnly = true;
}
// If the initailization fails after transactionManager has been created this method better cleans up
// or the FileSyncThread for the journal can/will hang.
try {
symbols = new SymbolTable(this, conf);
isReadOnly = isReadOnly || !symbols.getFile().canWrite();
indexManager = new IndexManager(this, conf);
//TODO : replace the following code by get()/release() statements ?
// WM: I would rather tend to keep this broker reserved as a system broker.
// create a first broker to initialize the security manager
//createBroker();
//TODO : this broker is *not* marked as active and *might* be reused by another process ! Is it intended ?
// at this stage, the database is still single-threaded, so reusing the broker later is not a problem.
//DBBroker broker = inactiveBrokers.peek();
// dmitriy: Security issue: better to use proper get()/release() way, because of subprocesses (SecurityManager as example)
final DBBroker broker = get(securityManager.getSystemSubject());
try {
if(isReadOnly()) {
transactionManager.setEnabled(false);
}
//Run the recovery process
//TODO : assume
boolean recovered = false;
if(isTransactional()) {
recovered = transactionManager.runRecovery(broker);
//TODO : extract the following from this block ? What if we ware not transactional ? -pb
if(!recovered) {
try {
if(broker.getCollection(XmldbURI.ROOT_COLLECTION_URI) == null) {
final Txn txn = transactionManager.beginTransaction();
try {
//TODO : use a root collection final member
broker.getOrCreateCollection(txn, XmldbURI.ROOT_COLLECTION_URI);
transactionManager.commit(txn);
} catch (final IOException e) {
transactionManager.abort(txn);
} catch (final PermissionDeniedException e) {
transactionManager.abort(txn);
} catch (final TriggerException e) {
transactionManager.abort(txn);
} finally {
transactionManager.close(txn);
}
}
} catch(final PermissionDeniedException pde) {
LOG.fatal(pde.getMessage(), pde);
}
}
}
/* initialise required collections if they dont exist yet */
if(!exportOnly) {
try {
initialiseSystemCollections(broker);
} catch(final PermissionDeniedException pde) {
LOG.error(pde.getMessage(), pde);
throw new EXistException(pde.getMessage(), pde);
}
}
//create the plugin manager
pluginManager = new PluginsManagerImpl(this, broker);
//TODO : from there, rethink the sequence of calls.
// WM: attention: a small change in the sequence of calls can break
// either normal startup or recovery.
status = OPERATING;
statusReporter.setStatus(SIGNAL_READINESS);
//Get a manager to handle further collections configuration
initCollectionConfigurationManager(broker);
//wake-up the plugins manager
pluginManager.start(broker);
//wake-up the security manager
securityManager.attach(this, broker);
//have to do this after initializing = false
// so that the policies collection is saved
if(securityManager.isXACMLEnabled()) {
securityManager.getPDP().initializePolicyCollection();
}
//If necessary, launch a task to repair the DB
//TODO : merge this with the recovery process ?
//XXX: don't do if READONLY mode
if(recovered) {
if(!exportOnly) {
reportStatus("Reindexing database files...");
try {
broker.repair();
} catch (final PermissionDeniedException e) {
LOG.warn("Error during recovery: " + e.getMessage(), e);
}
}
if(((Boolean)conf.getProperty(PROPERTY_RECOVERY_CHECK)).booleanValue()) {
final ConsistencyCheckTask task = new ConsistencyCheckTask();
final Properties props = new Properties();
props.setProperty("backup", "no");
props.setProperty("output", "sanity");
task.configure(conf, props);
task.execute(broker);
}
}
//OK : the DB is repaired; let's make a few RW operations
statusReporter.setStatus(SIGNAL_WRITABLE);
//initialize configurations watcher trigger
if(!exportOnly) {
try {
initialiseTriggersForCollections(broker, XmldbURI.SYSTEM_COLLECTION_URI);
} catch(final PermissionDeniedException pde) {
//XXX: do not catch exception!
LOG.error(pde.getMessage(), pde);
}
}
// remove temporary docs
try {
broker.cleanUpTempResources(true);
} catch(final PermissionDeniedException pde) {
LOG.error(pde.getMessage(), pde);
}
sync(broker, Sync.MAJOR_SYNC);
//require to allow access by BrokerPool.getInstance();
instances.put(instanceName, this);