package 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. import java.io.*; import java.sql.*; import java.util.*; import java.util.concurrent.locks.*; import javax.mail.internet.*; import javax.script.*; import votorola.a.*; import votorola.a.count.*; import votorola.a.response.*; import votorola.g.*; import votorola.g.lang.*; import votorola.g.mail.*; import votorola.g.script.*; /** The meta-service of the mail-based voter interface. It provides bootstrap * instructions on accessing the voter services, as well as general information about the * vote-server. In addition to replying to messages it receives at its own email address, * it also replies to any misaddressed mail that lands in the inbox, and is not * assignable to any other service. * * @see http://reluk.ca/project/votorola/s/mail/guide.xht#meta-service */ public @ThreadRestricted("holds lock()") final class MailMetaService extends VoterService { /** Constructs a MailMetaService. * * @param s the compiled startup configuration script */ public static @ThreadSafe MailMetaService newMetaService( VoteServer.Run vsRun, JavaScriptIncluder s ) throws IOException, ScriptException, SQLException { return new MailMetaService( vsRun, ConstructionContext.configure( vsRun.voteServer(), s )); } MailMetaService( final VoteServer.Run run, final ConstructionContext cc ) throws IOException, ScriptException, SQLException { super( run, cc ); // run.init_ensureAllVoterServices(); // this meta-service depends on all other services init( new ArrayList() ); constructionContext = null; // done with it, free the memory } private void init_2() { final ArrayList sList_poll = new ArrayList(); final ArrayList sList_other = new ArrayList(); for( VoterService service: vsRun.newVoterServiceArray() ) { if( service instanceof PollService ) sList_poll.add( (PollService)service ); else sList_other.add( service ); } serviceArray_polls = sList_poll.toArray( new PollService[sList_poll.size()] ); serviceArray_other = sList_other.toArray( new VoterService[sList_other.size()] ); } private ConstructionContext cc() { return (ConstructionContext)constructionContext; } // nulled after init // ```````````````````````````````````````````````````````````````````````````````````` // init for early use private final File startupConfigurationFile = cc().startupConfigurationFile(); // ------------------------------------------------------------------------------------ /** The runtime configuration file for this meta-service. The language is JavaScript. * There are restrictions on the {@linkplain votorola.g.script.JavaScriptIncluder * character encoding}. * * @see mail-meta-service-run.js (example script) * @see ../manual.xht#mail-meta-service-run.js */ @Warning( "thread restricted object, holds lock()" ) public JavaScriptIncluder runtimeConfigurationScript() { assert lock.isHeldByCurrentThread(); // this method is safe, but not the object return runtimeConfigurationScript; } private final JavaScriptIncluder runtimeConfigurationScript = new JavaScriptIncluder( new File( serviceDirectory(), "mail-meta-service-run.js" )); /** Constructs the email address of a voter service. * * @return canonical email address * * @see #serviceName(InternetAddress) * @see InternetAddressX#canonicalAddress(String) */ public String serviceEmail( final VoterService service ) { assert lock.isHeldByCurrentThread(); try { final String serviceEmail = (String)runtimeConfigurationScript.invokeKnownFunction( "serviceEmail", service ); return InternetAddressX.canonicalAddress( serviceEmail ); } catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } } /** Converts service email address into a service name. * * @see #serviceEmail(VoterService) */ public String serviceName( final InternetAddress serviceEmail ) { assert lock.isHeldByCurrentThread(); try { final String serviceName = (String)runtimeConfigurationScript.invokeKnownFunction( "serviceName", InternetAddressX.localPart( serviceEmail )); return serviceName; } catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } } // - V o t e r - S e r v i c e -------------------------------------------------------- public @Override Exception help( final String[] argv, final CommandResponder.Session session ) { assert lock.isHeldByCurrentThread(); final ReplyBuilder replyB = session.replyBuilder(); helpA( session ); replyB.lappendlnn( "s.mail.MailMetaService.help.reply.summary.trailer(1)", name ); helpB( session ); helpC( session ); final String generalTitle = replyB.bundle().getString( "s.mail.MailMetaService.help.reply.general" ); replyB.setWrapping( false ).appendln( generalTitle ); for( int c = generalTitle.length(); c > 0; --c ) replyB.append( '=' ); replyB.appendlnn().setWrapping( true ); replyB.indent( 4 ); replyB.lappendlnn( "s.mail.MailMetaService.help.reply.general.body" ); replyB.indent( 4 ); replyB.lappendlnn( "a.response.CR_Help.commandName" ); replyB.exdent( 8 ); if( serviceArray_other == null ) init_2(); // lazily, after all services have been created (not just the standard ones), including this meta-service assert serviceArray_other.length > 0: "this meta-service is listed, at the very least"; if( serviceArray_polls.length > 0 ) { listServices( replyB.bundle().getString( "s.mail.MailMetaService.serviceTypeTitle.polls" ), serviceArray_polls, session ); } listServices( replyB.bundle().getString( "s.mail.MailMetaService.serviceTypeTitle.other" ), serviceArray_other, session ); return null; } /** @see ../manual.xht#mail-meta-service.js */ public @ThreadSafe @Override File startupConfigurationFile() { return startupConfigurationFile; } /** A brief description of this meta-service, same as {@linkplain * VoteServer#summaryDescription() the vote-server's}. */ public @Override String summaryDescription() { return vsRun.voteServer().summaryDescription(); } /** The title of this meta-service, which is the same as the {@linkplain * VoteServer#title() vote-server's title}. */ public @Override String title() { return vsRun.voteServer().title(); } // ==================================================================================== /** A context for configuring the mail interface's {@linkplain MailMetaService voter * meta-service}. The meta-service is configured by its {@linkplain * #startupConfigurationFile startup configuration file}, which contains a script (s) * for that purpose. During construction of the meta-service, an instance of this * context (metaCC) is passed to s, via s::constructingMailMetaService(metaCC). * *

There are no configuration items, at present.

*/ public static @ThreadSafe final class ConstructionContext extends VoterService.ConstructionContext { /** Constructs the complete configuration of the meta-service, and runs sanity * tests on it. * * @param s the compiled startup configuration script */ public static ConstructionContext configure( final VoteServer voteServer, JavaScriptIncluder s ) throws ScriptException { final ConstructionContext cc = new ConstructionContext( voteServer, s ); s.invokeKnownFunction( "constructingMailMetaService", cc ); return cc; } private ConstructionContext( VoteServer voteServer, JavaScriptIncluder s ) { // super( voteServer.name(), s ); /// but since names aren't supposed to change anymore, this is better: super( "mail", s ); } // -------------------------------------------------------------------------------- // nothing here, at the moment } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private void listServices( final String serviceTypeTitle, final VoterService[] serviceArray, final CommandResponder.Session session ) { assert lock.isHeldByCurrentThread(); final ReplyBuilder replyB = session.replyBuilder(); replyB.setWrapping( false ).appendln( serviceTypeTitle ); for( int c = serviceTypeTitle.length(); c > 0; --c ) replyB.append( '=' ); replyB.appendlnn().setWrapping( true ).indent( 4 ); for( VoterService service: serviceArray ) { replyB.appendlnn( service.name() ); replyB.indent( 4 ); replyB.appendln( service.title() ); replyB.appendlnn( serviceEmail( service )); if( service.equals( MailMetaService.this )) { replyB.lappendlnn( "s.mail.MailMetaService.help.reply.self" ); } replyB.exdent( 4 ); } replyB.exdent( 4 ); } private PollService[] serviceArray_polls; // final after init_2() private VoterService[] serviceArray_other; // final after init_2() }