001package votorola.s.mail; // Copyright 2007-2009, 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.sql.*;
005import java.util.*;
006import java.util.concurrent.locks.*;
007import javax.mail.internet.*;
008import javax.script.*;
009import votorola.a.*;
010import votorola.a.count.*;
011import votorola.a.response.*;
012import votorola.g.*;
013import votorola.g.lang.*;
014import votorola.g.mail.*;
015import votorola.g.script.*;
016
017
018/** The meta-service of the mail-based voter interface.  It provides bootstrap
019  * instructions on accessing the voter services, as well as general information about the
020  * vote-server.  In addition to replying to messages it receives at its own email address,
021  * it also replies to any misaddressed mail that lands in the inbox, and is not
022  * assignable to any other service.
023  *
024  *     @see <a href='http://reluk.ca/project/votorola/s/mail/guide.xht#meta-service'
025  *                  >http://reluk.ca/project/votorola/s/mail/guide.xht#meta-service</a>
026  */
027public @ThreadRestricted("holds lock()") final class MailMetaService extends VoterService
028{
029
030    /** Constructs a MailMetaService.
031      *
032      *     @param s the compiled startup configuration script
033      */
034    public static @ThreadSafe MailMetaService newMetaService( VoteServer.Run vsRun,
035      JavaScriptIncluder s ) throws IOException, ScriptException, SQLException
036    {
037        return new MailMetaService( vsRun, ConstructionContext.configure( vsRun.voteServer(), s ));
038    }
039
040
041
042    MailMetaService( final VoteServer.Run run, final ConstructionContext cc )
043      throws IOException, ScriptException, SQLException
044    {
045        super( run, cc );
046//      run.init_ensureAllVoterServices(); // this meta-service depends on all other services
047        init( new ArrayList<CommandResponder>() );
048
049        constructionContext = null; // done with it, free the memory
050    }
051
052
053
054    private void init_2()
055    {
056        final ArrayList<PollService> sList_poll = new ArrayList<PollService>();
057        final ArrayList<VoterService> sList_other = new ArrayList<VoterService>();
058        for( VoterService service: vsRun.newVoterServiceArray() )
059        {
060            if( service instanceof PollService ) sList_poll.add( (PollService)service );
061            else sList_other.add( service );
062        }
063        serviceArray_polls = sList_poll.toArray( new PollService[sList_poll.size()] );
064        serviceArray_other = sList_other.toArray( new VoterService[sList_other.size()] );
065    }
066
067
068
069    private ConstructionContext cc() { return (ConstructionContext)constructionContext; } // nulled after init
070
071
072
073   // ````````````````````````````````````````````````````````````````````````````````````
074   // init for early use
075
076
077    private final File startupConfigurationFile = cc().startupConfigurationFile();
078
079
080
081   // ------------------------------------------------------------------------------------
082
083
084    /** The runtime configuration file for this meta-service.  The language is JavaScript.
085      * There are restrictions on the {@linkplain votorola.g.script.JavaScriptIncluder
086      * character encoding}.
087      *
088      *     @see <a href='../../../../../s/_/example/full/home/vdev/votorola/mail/mail-meta-service-run.js'
089      *                                                                   >mail-meta-service-run.js (example script)</a>
090      *     @see <a href='../../../../../s/manual.xht#mail-meta-service-run.js'
091      *                                >../manual.xht#mail-meta-service-run.js</a>
092      */
093      @Warning( "thread restricted object, holds lock()" )
094    public JavaScriptIncluder runtimeConfigurationScript()
095    {
096        assert lock.isHeldByCurrentThread(); // this method is safe, but not the object
097        return runtimeConfigurationScript;
098    }
099
100
101        private final JavaScriptIncluder runtimeConfigurationScript = new JavaScriptIncluder(
102          new File( serviceDirectory(), "mail-meta-service-run.js" ));
103
104
105
106    /** Constructs the email address of a voter service.
107      *
108      *     @return canonical email address
109      *
110      *     @see #serviceName(InternetAddress)
111      *     @see InternetAddressX#canonicalAddress(String)
112      */
113    public String serviceEmail( final VoterService service )
114    {
115        assert lock.isHeldByCurrentThread();
116        try
117        {
118            final String serviceEmail = (String)runtimeConfigurationScript.invokeKnownFunction(
119              "serviceEmail", service );
120            return InternetAddressX.canonicalAddress( serviceEmail );
121        }
122        catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); }
123    }
124
125
126
127    /** Converts service email address into a service name.
128      *
129      *     @see #serviceEmail(VoterService)
130      */
131    public String serviceName( final InternetAddress serviceEmail )
132    {
133        assert lock.isHeldByCurrentThread();
134        try
135        {
136            final String serviceName = (String)runtimeConfigurationScript.invokeKnownFunction(
137              "serviceName", InternetAddressX.localPart( serviceEmail ));
138
139            return serviceName;
140        }
141        catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); }
142    }
143
144
145
146   // - V o t e r - S e r v i c e --------------------------------------------------------
147
148
149    public @Override Exception help( final String[] argv, final CommandResponder.Session session )
150    {
151        assert lock.isHeldByCurrentThread();
152        final ReplyBuilder replyB = session.replyBuilder();
153        helpA( session );
154        replyB.lappendlnn( "s.mail.MailMetaService.help.reply.summary.trailer(1)", name );
155        helpB( session );
156        helpC( session );
157
158        final String generalTitle =
159          replyB.bundle().getString( "s.mail.MailMetaService.help.reply.general" );
160        replyB.setWrapping( false ).appendln( generalTitle );
161        for( int c = generalTitle.length(); c > 0; --c ) replyB.append( '=' );
162        replyB.appendlnn().setWrapping( true );
163        replyB.indent( 4 );
164        replyB.lappendlnn( "s.mail.MailMetaService.help.reply.general.body" );
165        replyB.indent( 4 );
166        replyB.lappendlnn( "a.response.CR_Help.commandName" );
167        replyB.exdent( 8 );
168
169        if( serviceArray_other == null ) init_2(); // lazily, after all services have been created (not just the standard ones), including this meta-service
170        assert serviceArray_other.length > 0: "this meta-service is listed, at the very least";
171        if( serviceArray_polls.length > 0 )
172        {
173            listServices(
174              replyB.bundle().getString( "s.mail.MailMetaService.serviceTypeTitle.polls" ),
175              serviceArray_polls, session );
176        }
177        listServices( replyB.bundle().getString( "s.mail.MailMetaService.serviceTypeTitle.other" ),
178          serviceArray_other, session );
179        return null;
180    }
181
182
183
184    /** @see <a href='../../../../../s/manual.xht#mail-meta-service.js'
185      *                            >../manual.xht#mail-meta-service.js</a>
186      */
187    public @ThreadSafe @Override File startupConfigurationFile()
188    {
189        return startupConfigurationFile;
190    }
191
192
193
194    /** A brief description of this meta-service, same as {@linkplain
195      * VoteServer#summaryDescription() the vote-server's}.
196      */
197    public @Override String summaryDescription() { return vsRun.voteServer().summaryDescription(); }
198
199
200
201    /** The title of this meta-service, which is the same as the {@linkplain
202      * VoteServer#title() vote-server's title}.
203      */
204    public @Override String title() { return vsRun.voteServer().title(); }
205
206
207
208   // ====================================================================================
209
210
211    /** A context for configuring the mail interface's {@linkplain MailMetaService voter
212      * meta-service}.  The meta-service is configured by its {@linkplain
213      * #startupConfigurationFile startup configuration file}, which contains a script (s)
214      * for that purpose.  During construction of the meta-service, an instance of this
215      * context (metaCC) is passed to s, via s::constructingMailMetaService(metaCC).
216      *
217      * <p>There are no configuration items, at present.</p>
218      */
219    public static @ThreadSafe final class ConstructionContext
220      extends VoterService.ConstructionContext
221    {
222
223
224        /** Constructs the complete configuration of the meta-service, and runs sanity
225          * tests on it.
226          *
227          *     @param s the compiled startup configuration script
228          */
229        public static ConstructionContext configure( final VoteServer voteServer,
230          JavaScriptIncluder s ) throws ScriptException
231        {
232            final ConstructionContext cc = new ConstructionContext( voteServer, s );
233            s.invokeKnownFunction( "constructingMailMetaService", cc );
234            return cc;
235        }
236
237
238
239        private ConstructionContext( VoteServer voteServer, JavaScriptIncluder s )
240        {
241         // super( voteServer.name(), s );
242         /// but since names aren't supposed to change anymore, this is better:
243            super( "mail", s );
244        }
245
246
247
248       // --------------------------------------------------------------------------------
249
250
251        // nothing here, at the moment
252
253
254    }
255
256
257
258//// P r i v a t e ///////////////////////////////////////////////////////////////////////
259
260
261    private void listServices( final String serviceTypeTitle,
262      final VoterService[] serviceArray, final CommandResponder.Session session )
263    {
264        assert lock.isHeldByCurrentThread();
265        final ReplyBuilder replyB = session.replyBuilder();
266
267        replyB.setWrapping( false ).appendln( serviceTypeTitle );
268        for( int c = serviceTypeTitle.length(); c > 0; --c ) replyB.append( '=' );
269        replyB.appendlnn().setWrapping( true ).indent( 4 );
270        for( VoterService service: serviceArray )
271        {
272            replyB.appendlnn( service.name() );
273            replyB.indent( 4 );
274            replyB.appendln( service.title() );
275            replyB.appendlnn( serviceEmail( service ));
276            if( service.equals( MailMetaService.this ))
277            {
278                replyB.lappendlnn( "s.mail.MailMetaService.help.reply.self" );
279            }
280            replyB.exdent( 4 );
281        }
282        replyB.exdent( 4 );
283    }
284
285
286
287    private PollService[] serviceArray_polls; // final after init_2()
288
289
290    private VoterService[] serviceArray_other; // final after init_2()
291
292
293
294}