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}