001package votorola.a; // Copyright 2007-2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import java.io.*; 004import java.net.*; 005import java.sql.*; 006import java.util.*; 007import java.util.concurrent.atomic.*; 008import java.util.concurrent.locks.*; 009import java.util.logging.*; 010import javax.mail.internet.*; 011import javax.script.*; 012import org.postgresql.ds.*; 013import votorola.a.diff.*; 014import votorola.s.mail.*; 015import votorola.a.count.*; 016import votorola.a.trust.*; 017import votorola.a.voter.*; 018import votorola.g.*; 019import votorola.g.lang.*; 020import votorola.g.logging.*; 021import votorola.g.net.*; 022import votorola.g.script.*; 023import votorola.g.sql.*; 024 025 026/** A server that provides polls and other voter services. 027 * 028 * @see <a href='../../../../s/manual.xht#vote-server' target='_top' 029 * >../s/manual.xht#vote-server</a> 030 */ 031public @ThreadSafe final class VoteServer 032{ 033 034 035 /** Constructs a VoteServer. 036 */ 037 public VoteServer( final String name ) throws IOException, ScriptException, URISyntaxException 038 { 039 this.name = name; 040 041 votorolaDirectory = new File( BASH.homeDirectory(name), "votorola" ); 042 // not relying on property user.home, as user might not be vote-server 043 startupConfigurationFile = new File( votorolaDirectory, "vote-server.js" ); 044 final ConstructionContext cc = ConstructionContext.configure( name, votorolaDirectory, 045 new JavaScriptIncluder( startupConfigurationFile )); 046 047 serverName = cc.getServerName(); 048 shortTitle = cc.getShortTitle(); 049 summaryDescription = cc.getSummaryDescription(); 050 testUseMode = cc.getTestUseMode(); 051 title = cc.getTitle(); 052 votorolaURI = cc.getVotorolaURI(); 053 054 codeDirectory = new File( votorolaDirectory(), "code" ); 055 inDirectory = new File( votorolaDirectory(), "in" ); 056 outDirectory = new File( votorolaDirectory(), "out" ); 057 058 database = new Database( cc.database() ); 059 diffCache = new DiffCache( VoteServer.this, cc ); 060 pollwiki = new PollwikiVS( cc.pollwiki() ); 061 pollwiki.init( VoteServer.this ); 062 scopeCount = new Count.VoteServerScope( VoteServer.this ); 063 scopePoll = new PollService.VoteServerScope( VoteServer.this, cc ); 064 scopeTrace = new NetworkTrace.VoteServerScope( VoteServer.this ); 065 } 066 067 068 069 // ------------------------------------------------------------------------------------ 070 071 072 /** The directory <code>~/votorola/code</code> in which vote-server's runtime code is 073 * stored. 074 */ 075 public File codeDirectory() { return codeDirectory; } 076 077 078 private final File codeDirectory; 079 080 081 082 /** The cache of <code>diff</code> output. 083 */ 084 public DiffCache diffCache() { return diffCache; } 085 086 087 private final DiffCache diffCache; 088 089 090 091 /** The base directory <code>~/votorola/in</code> of the file portion of the input 092 * store. It serves mostly as a cache of files fetched from external sources. 093 * 094 * @see InputStore 095 */ 096 public File inDirectory() { return inDirectory; } 097 098 099 private final File inDirectory; 100 101 102 103 /** The login name of the local host account, under which the vote-server's data is 104 * stored and its processes are run. 105 * 106 * @see #serverName() 107 * @see <a href="../../../../s/manual.xht#vote-server-name" 108 * >../s/manual.xht#vote-server-name</a> 109 */ 110 public String name() { return name; } 111 112 113 private final String name; 114 115 116 117 /** The base directory <code>~/votorola/out</code> of the file portion of the output 118 * store. 119 * 120 * @see OutputStore 121 */ 122 public File outDirectory() { return outDirectory; } 123 124 125 private final File outDirectory; 126 127 128 129 /** The pollwiki associated with this vote-server. 130 */ 131 public PollwikiVS pollwiki() { return pollwiki; } 132 133 134 private final PollwikiVS pollwiki; 135 136 137 138 /** API for all counts within the scope of this vote-server. 139 */ 140 public Count.VoteServerScope scopeCount() { return scopeCount; } 141 142 143 private final Count.VoteServerScope scopeCount; 144 145 146 147 /** API for all polls within the scope of this vote-server. 148 */ 149 public PollService.VoteServerScope scopePoll() { return scopePoll; } 150 151 152 private final PollService.VoteServerScope scopePoll; 153 154 155 156 /** API for all traces of trust networks within the scope of this vote-server. 157 */ 158 public NetworkTrace.VoteServerScope scopeTrace() { return scopeTrace; } 159 160 161 private final NetworkTrace.VoteServerScope scopeTrace; 162 163 164 165 /** The name of the computer on which this vote-server is hosted. For example 166 * "localhost", "hostname" or (fully qualified) "hostname.mydomain.dom". It is used 167 * to construct service and return email addresses for the various "<a 168 * href='../../index-files/index-19.html'>serviceEmail</a>" methods. It is also used 169 * to construct the default URL to the wiki. In a public facing server, this should 170 * be a fully qualified name. 171 * 172 * @see #name() 173 * @see ConstructionContext#setServerName(String) 174 */ 175 public String serverName() { return serverName; } 176 177 178 private final String serverName; 179 180 181 182 /** A short version of the {@linkplain #title() title}, restricted to roughly 183 * {@linkplain votorola.a.web.wic.VPage#SHORT_STRING_LENGTH_MAX SHORT_STRING_LENGTH_MAX} 184 * characters. For example, "Toronto". 185 * 186 * @see #title() 187 * @see ConstructionContext#setTitle(String,String) 188 */ 189 public String shortTitle() { return shortTitle; } 190 191 192 private final String shortTitle; 193 194 195 196 /** The startup configuration file for this vote-server. It is located at: 197 * 198 * <p class='indent'>{@linkplain #votorolaDirectory() 199 * votorolaDirectory}/vote-server.js</p> 200 * 201 * <p>The language is JavaScript. There are restrictions on the {@linkplain 202 * votorola.g.script.JavaScriptIncluder character encoding}.</p> 203 */ 204 File startupConfigurationFile() { return startupConfigurationFile; } 205 206 207 private final File startupConfigurationFile; 208 209 210 211 /** A brief description of this vote-server, in sentence form. 212 * It is intended for display, for example, as an introductory paragraph. 213 * 214 * @see ConstructionContext#setSummaryDescription(String) 215 */ 216 public String summaryDescription() { return summaryDescription; } 217 218 219 private final String summaryDescription; 220 221 222 223 /** The test use mode of this vote-server, which determines whether users may log in 224 * under aliases for test purposes. 225 * 226 * @see ConstructionContext#setTestUseMode(VoteServer.TestUseMode) 227 */ 228 public TestUseMode testUseMode() { return testUseMode; } 229 230 231 private final TestUseMode testUseMode; 232 233 234 235 /** The title of this vote-server, in wiki-style (first word only) title case. If the 236 * vote-server serves a single deployment area, such as a town or region, then it will 237 * normally take the title of that area. For example, "City of Toronto". 238 * 239 * @see #shortTitle() 240 * @see ConstructionContext#setTitle(String,String) 241 * @see <a href='http://reluk.ca/project/_/outcast/dep.xht#area'>Deployment areas</a> 242 */ 243 public String title() { return title; } 244 245 246 private final String title; 247 248 249 250 /** The base directory <code>~/votorola</code> of all vote-server configuration files, 251 * where <code>~</code> is the home directory of the vote-server account. 252 */ 253 public File votorolaDirectory() { return votorolaDirectory; } 254 255 256 private final File votorolaDirectory; 257 258 259 260 /** The public location of the {@linkplain #votorolaDirectory() votorola directory} 261 * without a trailing slash (/), or a local "file" URI if the directory is 262 * unpublished. Not all files within the directory are necessarily visible or 263 * readable by everyone. 264 * 265 * @see ConstructionContext#setVotorolaLocation(String) 266 * @see ConstructionContext#setVotorolaURI(URI) 267 */ 268 public URI votorolaURI() { return votorolaURI; } 269 270 271 private final URI votorolaURI; 272 273 274 275 // ==================================================================================== 276 277 278 /** A context for configuring the construction of a {@linkplain VoteServer 279 * VoteServer}. The construction is configured by the vote-server's {@linkplain 280 * VoteServer#startupConfigurationFile() startup configuration file}, which contains 281 * a script (s) for that purpose. During construction, an instance of this context 282 * (vsCC) is passed to s via s::constructingVoteServer(vsCC). 283 */ 284 public static @ThreadSafe final class ConstructionContext 285 { 286 287 288 /** Constructs the complete configuration of the vote-server. 289 * 290 * @param _name the name of the vote-server. 291 * @param s the compiled startup configuration script. 292 */ 293 private static ConstructionContext configure( String _name, final File votorolaDirectory, 294 final JavaScriptIncluder s ) throws ScriptException, URISyntaxException 295 { 296 final ConstructionContext cc = new ConstructionContext( _name, s ); 297 s.invokeKnownFunction( "constructingVoteServer", cc ); 298 if( cc.votorolaURI == null ) 299 { 300 final StringBuilder b = new StringBuilder( 301 votorolaDirectory.toURI().toASCIIString() ); 302 { 303 final int cLast = b.length() - 1; 304 if( b.charAt(cLast) == '/' ) b.deleteCharAt( cLast ); 305 } 306 cc.setVotorolaLocation( b.toString() ); 307 } 308 cc.pollwiki.postConfigure( cc ); 309 return cc; 310 } 311 312 313 314 private ConstructionContext( final String name, final JavaScriptIncluder s ) 315 { 316 this.name = name; 317 startupConfigurationFile = s.scriptFile(); 318 319 database = new DatabaseCC( name ); 320 summaryDescription = "This is a vote-server. Further information " 321 + "is unavailable, because the 'constructingVoteServer' function " 322 + "of script " + startupConfigurationFile + " " 323 + "makes no call to 'setSummaryDescription'."; 324 } 325 326 327 328 private final File startupConfigurationFile; 329 330 331 332 // -------------------------------------------------------------------------------- 333 334 335 /** The context for configuring the vote-server's relational database. 336 */ 337 public DatabaseCC database() { return database; } 338 339 340 private final DatabaseCC database; 341 342 343 344 /** @see votorola.a.count.PollService.VoteServerScope#pollCacheCapacity() 345 * @see #setPollCacheCapacity(int) 346 */ 347 public int getPollCacheCapacity() { return pollCacheCapacity; } 348 349 350 private int pollCacheCapacity = 500; 351 352 353 /** Sets the maximum number of entries in the poll cache. The default value 354 * is 500 polls. 355 * 356 * @see votorola.a.count.PollService.VoteServerScope#pollCacheCapacity() 357 */ 358 @ThreadRestricted("constructor") 359 public void setPollCacheCapacity( int _pollCacheCapacity ) 360 { 361 pollCacheCapacity = _pollCacheCapacity; 362 } 363 364 365 366 /** @see VoteServer#serverName() 367 * @see #setServerName(String) 368 */ 369 public String getServerName() { return serverName; } 370 371 372 private String serverName; 373 374 { 375 serverName = "localhost"; 376 try 377 { 378 final InetAddress a = InetAddress.getLocalHost(); 379 final String name = a.getHostName(); 380 if( name.equals( a.getHostAddress() )) 381 { 382 logger.config( "unable to resolve default serverName, reverting to 'localhost'" ); 383 } 384 else serverName = name; 385 } 386 catch( UnknownHostException x ) 387 { 388 logger.config( "unable to resolve default serverName, reverting to 'localhost': " + x ); 389 } 390 } 391 392 393 /** Specifies the fully qualified name of the computer on which this 394 * vote-server is hosted. The default value is the system hostname as 395 * determined at runtime, or failing that "localhost". It is recommended 396 * that you explicitly set "localhost" or a fully qualified name. 397 * 398 * @see VoteServer#serverName() 399 */ 400 @ThreadRestricted("constructor") 401 public void setServerName( final String serverName ) { this.serverName = serverName; } 402 // If not correctly set, redirection links such as "my draft" may fail to 403 // convey state as expected. See logged warning in 404 // votorola.s.wic.WP_Draft.maybeRedirectToDraft. 405 406 407 408 /** @see VoteServer#shortTitle() 409 * @see #setTitle(String,String) 410 */ 411 public String getShortTitle() { return shortTitle; } 412 413 414 private String shortTitle = "Votorola"; 415 416 417 418 /** @see VoteServer#summaryDescription() 419 * @see #setSummaryDescription(String) 420 */ 421 public String getSummaryDescription() { return summaryDescription; } 422 423 424 private String summaryDescription; 425 426 427 /** Sets the summary description of the vote-server. The default value is a 428 * placeholder with configuration instructions for the administrator. 429 * 430 * @throws IllegalArgumentException if the description contains 431 * any newline characters, because they might render inconsistently 432 * across different types of user interface. 433 * @see VoteServer#summaryDescription() 434 */ 435 @ThreadRestricted("constructor") 436 public void setSummaryDescription( final String summaryDescription ) 437 { 438 if( summaryDescription.indexOf('\n') >= 0 ) throw new IllegalArgumentException( "argument contains a newline character" ); 439 440 this.summaryDescription = summaryDescription; 441 } 442 443 444 445 /** @see VoteServer#testUseMode() 446 * @see #setTestUseMode(VoteServer.TestUseMode) 447 */ 448 public TestUseMode getTestUseMode() { return testUseMode; } 449 450 451 private TestUseMode testUseMode = TestUseMode.OFF; 452 453 454 /** Sets the test use mode of the vote-server. The default value is 455 * TestUseMode.OFF. 456 * 457 * @see VoteServer#testUseMode() 458 */ 459 @ThreadRestricted("constructor") 460 public void setTestUseMode( final TestUseMode testUseMode ) 461 { 462 this.testUseMode = testUseMode; 463 } 464 465 466 467 /** @see VoteServer#title() 468 * @see #setTitle(String,String) 469 */ 470 public String getTitle() { return title; } 471 472 473 private String title = "Votorola vote-server"; 474 475 476 /** Sets the title of the vote-server. The default values for the long and 477 * short titles are "Votorola vote-server" and "Votorola". 478 * 479 * @see VoteServer#title() 480 * @see VoteServer#shortTitle() 481 */ 482 @ThreadRestricted("constructor") 483 public void setTitle( final String title, final String shortTitle ) 484 { 485 this.title = title; 486 this.shortTitle = shortTitle; 487 } 488 489 490 491 /** The name of the vote-server. 492 * 493 * @see VoteServer#name() 494 */ 495 public String name() { return name; } 496 497 498 private final String name; 499 500 501 502 /** The context for configuring the vote-server's pollwiki. 503 */ 504 public PollwikiVS.ConstructionContext pollwiki() { return pollwiki; } 505 506 507 private final PollwikiVS.ConstructionContext pollwiki = 508 new PollwikiVS.ConstructionContext(); 509 510 511 512 /** The public location of the {@linkplain VoteServer#votorolaDirectory() votorola 513 * directory}, or null for the default. 514 * 515 * @see VoteServer#votorolaURI() 516 * @see #setVotorolaLocation(String) 517 * @see #setVotorolaURI(URI) 518 */ 519 public URI getVotorolaURI() { return votorolaURI; } 520 521 522 private URI votorolaURI = null; 523 524 525 /** Sets the public location of the {@linkplain VoteServer#votorolaDirectory() 526 * votorola directory}. The default value is null, which translates at 527 * runtime to a local "file" URI. 528 * 529 * @see VoteServer#votorolaURI() 530 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 531 */ 532 @ThreadRestricted("constructor") 533 public void setVotorolaLocation( final String s ) throws URISyntaxException 534 { 535 setVotorolaURI( new URI( s )); 536 } 537 538 539 /** Sets the public location of the {@linkplain VoteServer#votorolaDirectory() 540 * votorola directory}. The default value is null, which translates at 541 * runtime to a local "file" URI. 542 * 543 * @see VoteServer#votorolaURI() 544 * @throws IllegalArgumentException if the URI ends with a slash '/' character. 545 */ 546 public @ThreadRestricted("constructor") void setVotorolaURI( final URI u ) 547 { 548 if( u.toString().endsWith( "/" )) 549 { 550 throw new IllegalArgumentException( "URI ends with '/'" ); 551 } 552 553 votorolaURI = u; 554 } 555 556 557 } 558 559 560 561 // ================================================================================ 562 563 564 /** A context for configuring the construction of a vote-server {@linkplain Database 565 * Database}. 566 */ 567 public static @ThreadSafe final class DatabaseCC extends Database.ConstructionContext 568 { 569 570 571 /** Constructs a DatabaseCC. 572 */ 573 public DatabaseCC( final String voteServerName ) 574 { 575 name = voteServerName; 576 username = voteServerName; 577 } 578 579 580 581 /** @see #setName(String) 582 */ 583 public @Override String getName() { return name; } 584 585 586 private String name; 587 588 589 /** Sets the name of the database. The default value is the {@linkplain 590 * VoteServer#name() vote-server name}. 591 * 592 * @see #getName() 593 */ 594 @ThreadRestricted("constructor") 595 public void setName( String name ) { this.name = name; } 596 597 598 599 /** @see #setUsername(String) 600 */ 601 public @Override String getUsername() { return username; } 602 603 604 private String username; 605 606 607 /** Sets the database user name. The default value is the {@linkplain 608 * VoteServer#name() vote-server name}. 609 * 610 * @see #getUsername() 611 */ 612 @ThreadRestricted("constructor") 613 public void setUsername( String username ) { this.username = username; } 614 615 616 617 } 618 619 620 621 // ==================================================================================== 622 623 624 /** A run of the vote-server. 625 */ 626 public @ThreadSafe final class Run 627 { 628 629 630 /** Constructs a Run. 631 * 632 * @param isSingleThreaded per {@linkplain #isSingleThreaded() isSingleThreaded}() - 633 * if false (multi-threaded), the client should call 634 * {@linkplain #init_done init_done}() after initialization is complete. 635 */ 636 public Run( final boolean isSingleThreaded ) 637 throws IOException, ScriptException, SQLException 638 { 639 if( isSingleThreaded ) singleServiceLock = new ReentrantLock(); 640 else singleServiceLock = null; 641 642 if( !outDirectory.isDirectory() ) 643 { 644 throw new IOException( "vote-server output store, no such directory: " + outDirectory ); // fail fast 645 // final String username = System.getProperty( "user.name" ); 646 // if( name.equals( username )) 647 // { 648 // if( !outDirectory.mkdirs() ) throw new IOException( "unable to create vote-server output directory " + outDirectory.getPath() ); 649 // } 650 // else throw new IOException( "unable to create vote-server output directory " + outDirectory + ", because current user '" + username + "' is not the vote-server account user '" + name + "'" ); // avoid creating directory having wrong owner, such as tomcat 651 } 652 653 // try 654 // { 655 database.init( new PGSimpleDataSource() ); 656 // } 657 // finally{ database.logAndClearWarnings(); } // to show SQLState 658 //// but no warnings recorded there 659 geocodeTable = new Geocode.Table( database ); 660 userTable = new UserSettings.Table( database ); 661 try 662 { 663 trustserver = init_ensureVoterService( new File( votorolaDirectory(), 664 "trust" + File.separatorChar + "trustserver.js" ), Trustserver.class ); 665 } 666 catch( ScriptException x ) { throw new MisconfigurationException( "unable to initialize trustserver", startupConfigurationFile, x ); } 667 668 scopePoll = VoteServer.this.scopePoll.new Run( Run.this ); 669 } 670 671 672 673 public @Warning( "non-API" ) final AtomicReference<Thread> constructionThreadA = 674 new AtomicReference<Thread>( Thread.currentThread() ); 675 676 677 678 /** Records that initialization is complete - that no further calls will be made 679 * to init_ methods. {@linkplain #isSingleThreaded() Single-threaded clients} 680 * need not call this method; it only enables thread-safety assertions in the 681 * init_ methods. [No longer true, as some late constructs will now throw an 682 * IllegalStateException if this method has not been called.] 683 */ 684 public void init_done() 685 { 686 assert Thread.currentThread().equals( constructionThreadA.get() ); 687 constructionThreadA.set( null ); 688 } 689 690 691 692 /** Returns a voter service, creating it if necessary and storing it for later 693 * retrieval. 694 * 695 * @param startupConfigurationFile the startup configuration file for the service. 696 * @param serviceClass the class of the service. 697 * 698 * @throws VoterService.NoSuchServiceException if startupConfigurationFile 699 * does not exist. 700 * 701 * @see #init_putVoterService(VoterService) 702 * @see #voterService(String) 703 */ 704 @ThreadRestricted("constructor") // so no worry about deadlock involving run and service locks held during atomic get/create/put of new service 705 public <S extends VoterService> S init_ensureVoterService( 706 final File startupConfigurationFile, final Class<S> serviceClass ) 707 throws IOException, ScriptException, SQLException 708 { 709 assert Thread.currentThread().equals( constructionThreadA.get() ); 710 VoterService s = voterService( startupConfigurationFile.getParentFile().getName() ); 711 if( s == null ) 712 { 713 final JavaScriptIncluder iJS; 714 { 715 // logger.config( "loading configuration: " + startupConfigurationFile ); // catch-all disclosure, particularly for VOTer 716 /// how is this useful? why not do same for all config files? 717 if( !startupConfigurationFile.isFile() ) throw new VoterService.NoSuchServiceException( "missing startup configuration file", startupConfigurationFile ); 718 719 iJS = new JavaScriptIncluder( startupConfigurationFile ); 720 } 721 722 // if( serviceClass.equals( PollService.class )) s = scopePoll.newPoll( iJS ); 723 if( serviceClass.equals( PollService.class )) throw new IllegalArgumentException( "attempt to construct poll, use scopePoll.ensurePoll() instead" ); 724 else if( serviceClass.equals( Trustserver.class )) s = Trustserver.newTrustserver( Run.this, iJS ); 725 else if( serviceClass.equals( MailMetaService.class )) s = MailMetaService.newMetaService( Run.this, iJS ); 726 else throw new IllegalArgumentException( "unknown service type: " + serviceClass ); 727 728 init_putVoterService( s ); 729 } 730 731 return serviceClass.cast( s ); 732 } 733 734 735 736 /** Stores a voter service in this run, for later retrieval. 737 * 738 * @throws IllegalStateException if an instance 739 * of the same service was already stored. 740 * 741 * @see #voterService(String) 742 */ 743 @ThreadRestricted("constructor") 744 public void init_putVoterService( VoterService service ) 745 { 746 assert Thread.currentThread().equals( constructionThreadA.get() ); 747 if( voterServiceMap.put( service.name(), service ) != null ) throw new IllegalStateException( "duplicate service: " + service ); 748 } 749 750 751 752 // -------------------------------------------------------------------------------- 753 754 755 /** The vote-server's relational database. 756 * 757 * @see ConstructionContext#database() 758 */ 759 public @Warning("thread restricted object") Database database() { return database; } 760 // OPT this bottleneck with separately constructed database and table 761 // interfaces, especially for processes that run in parallel 762 763 764 765 /** The relational cache of geocoded residential addresses. 766 * 767 * @see ConstructionContext#database() 768 */ 769 public Geocode.Table geocodeTable() { return geocodeTable; } 770 771 772 private final Geocode.Table geocodeTable; 773 774 775 776 /** Returns true if this run is restricted to a single client thread, false if 777 * multiple client threads are allowed. 778 */ 779 public boolean isSingleThreaded() { return singleServiceLock != null; } 780 781 782 783 /** Returns all non-poll voter services provided in this run. 784 * 785 * @return newly created array of voter services. 786 * 787 * @see #voterService(String) 788 */ 789 public VoterService[] newVoterServiceArray() 790 { 791 Collection<VoterService> values; 792 synchronized( Run.this ) { values = voterServiceMap.values(); } 793 return values.toArray( new VoterService[values.size()] ); 794 } 795 796 797 798 /** The vote-server for this run. 799 */ 800 public VoteServer voteServer() { return VoteServer.this; } 801 802 803 804 /** API for all polls within the scope of this vote-server run. 805 */ 806 public PollService.VoteServerScope.Run scopePoll() { return scopePoll; } 807 808 809 private final PollService.VoteServerScope.Run scopePoll; 810 811 812 813 /** Returns the thread access lock shared by all services; or null if all services 814 * do not share the same lock. They share the same lock only if the run is 815 * {@linkplain #isSingleThreaded() single threaded}. 816 * 817 * @see VoterService#lock() 818 */ 819 public ReentrantLock singleServiceLock() { return singleServiceLock; }; 820 821 822 final ReentrantLock singleServiceLock; // when single threaded, there is no need for a lock at all, but having one simpifies the coding of thread-safety assertions 823 824 825 826 /** The trustserver for this vote-server. 827 */ 828 @Warning("thread restricted object") public Trustserver trustserver() { return trustserver; } 829 830 831 private final Trustserver trustserver; 832 833 834 835 /** The relational store of service preferences and other settings, 836 * for the users of this vote-server. 837 * 838 * @see ConstructionContext#database() 839 */ 840 @Warning("thread restricted object") 841 public UserSettings.Table userTable() { return userTable; } 842 843 844 private final UserSettings.Table userTable; 845 846 847 848 /** Returns a non-poll voter service provided in this run; or null if the name 849 * designates no provided non-poll service. 850 * 851 * @param name per VoterService.{@linkplain VoterService#name() name}(). 852 * 853 * @see VoterService#isNonPoll(String) 854 * @see #init_ensureVoterService(File,Class) 855 * @see #newVoterServiceArray() 856 * @see votorola.a.count.PollService.VoteServerScope.Run#ensurePoll(String) 857 */ 858 public synchronized VoterService voterService( String name ) throws IOException, 859 ScriptException, SQLException 860 { 861 return voterServiceMap.get( name ); 862 } 863 864 @ThreadRestricted("holds Run.this") 865 private final HashMap<String,VoterService> voterServiceMap = 866 new HashMap<String,VoterService>(); 867 868 869 } 870 871 872 873 // ==================================================================================== 874 875 876 /** A mode of test usage for a vote-server. It allows a particular level of support 877 * for test users to login under unauthenticated aliases. Not all user interfaces 878 * are required to support this; currently only the web interface does. 879 */ 880 public static enum TestUseMode 881 { 882 883 /** A mode in which test users are disallowed. 884 */ 885 OFF, 886 887 888 /** A mode in which test users are allowed and their input is handled exactly as 889 * for authenticated users, except it is subject to arbitrary deletion by the 890 * administrator. 891 */ 892 FULL 893 894 } 895 896 897 898//// P r i v a t e /////////////////////////////////////////////////////////////////////// 899 900 901 private Database database; // database uninitialized till run commences 902 903 904 905 private static final Logger logger = LoggerX.i( VoteServer.class ); 906 907 908 909}