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}