001package votorola.s.line; // Copyright 2007-2010, 2012-2013, 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.regex.*; 007import javax.script.*; 008import votorola.a.*; 009import votorola.a.response.*; 010import votorola.a.response.line.*; 011import votorola.a.voter.*; 012import votorola.g.*; 013import votorola.g.lang.*; 014import votorola.g.locale.*; 015import votorola.g.logging.*; 016import votorola.g.option.*; 017import votorola.g.util.*; 018import votorola.s.mail.*; 019 020import static votorola.a.response.line.CommandLine.COMMAND_ARGUMENT_PATTERN; 021import static votorola.a.voter.IDPair.NOBODY; 022 023 024/** Main class of the executable <code>voter</code> - a shell for issuing service commands 025 * on behalf of a voter. 026 * 027 * @see <a href='../../../../../s/manual.xht#line-voter' target='_top'>voter</a> 028 */ 029public @ThreadRestricted final class VOTer implements VoterInterface 030{ 031 032 033 /** Executes from the command line. 034 * 035 * @param argv the command line argument array. 036 */ 037 public static void main( String[] argv ) { execute( argv, null ); } 038 039 040 041 private static void execute( String[] argv, HashMap<String,Option> optionMap ) 042 { 043 final boolean isPiped = optionMap != null; 044 LoggerX.i(VOTer.class).info( "voter shell " + (isPiped? "piped": "running") + " with arguments: " + Arrays.toString( argv )); 045 046 // Parse arguments. Split argv into argv for the VOTer shell only, and argvCommand 047 // for the command responder. 048 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 049 final String[] argvCommand; 050 String voterEmail = null; 051 String serviceName = null; 052 boolean tooManyIDs = false; 053 { 054 int a; 055 for( a = 0; a < argv.length; ++a ) 056 { 057 final String arg = argv[a]; 058 if( arg.charAt(0) == '-' ) continue; // skip past VOTer options 059 060 if( arg.indexOf('@') > 0 ) // swallow VOTER-EMAIL 061 { 062 if( voterEmail == null ) 063 { 064 voterEmail = arg; 065 continue; 066 } 067 else tooManyIDs = true; 068 } 069 if( serviceName == null ) // swallow SERVICE-NAME 070 { 071 serviceName = arg; 072 continue; 073 } 074 075 break; 076 } 077 argvCommand = Arrays.copyOfRange( argv, a, argv.length ); 078 // argvCommand[0] is the command itself, the arguments proper follow it 079 argv = Arrays.copyOfRange( argv, 0, a ); 080 } 081 082 // Parse options for VOTer. 083 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 084 if( optionMap == null ) 085 { 086 optionMap = CommandLine.compileBaseOptions(); 087 final String name = "id"; 088 optionMap.put( name, new Option( name, Option.REQUIRED_ARGUMENT )); 089 } 090 final int aFirstNonOption = GetoptX.parse( "voter", argv, optionMap ); 091 if( optionMap.get("help").hasOccured() ) 092 { 093 System.out.print( 094 "Usage: voter [--id=ID|VOTER-EMAIL] SERVICE-NAME COMMAND [OPTION...] [ARGUMENT...]\n" + 095 " or voter < ARGUMENT-FILE\n" + 096 " or cat ARGUMENT-FILE | voter\n" + 097 " or voter SERVICE-NAME help\n" + 098 " or voter --help\n" + 099 "Issue a service command on behalf of a voter.\n" ); 100 return; 101 } 102 103 final IDPair id; 104 { 105 String name = null; 106 if( !tooManyIDs ) 107 { 108 final Option idOption = optionMap.get( "id" ); 109 if( idOption.hasOccured() ) 110 { 111 if( voterEmail == null && idOption.occurenceCount() == 1 ) 112 { 113 final String idString = idOption.argumentValue(); 114 if( idString.indexOf('@') > 0 ) voterEmail = idString; 115 else name = idString; 116 } 117 else tooManyIDs = true; 118 } 119 } 120 if( tooManyIDs ) 121 { 122 System.err.println( "voter: too many personal identifiers (--id or VOTER-EMAIL)" ); 123 System.err.println( GetoptX.createHelpPrompt( "voter" )); 124 System.exit( 1 ); 125 } 126 127 if( name != null ) 128 { 129 IDPair id2 = NOBODY; 130 try{ id2 = IDPair.fromUsername( name ); } 131 catch( final javax.mail.internet.AddressException x ) 132 { 133 System.err.println( "voter: malformed id option: " + x.getMessage() ); 134 System.exit( 1 ); 135 } 136 137 id = id2; // just to prevent compiler error 138 } 139 else if( voterEmail != null ) id = IDPair.fromEmail( voterEmail ); 140 else id = NOBODY; 141 } 142 143 // Execute. 144 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 145 try 146 { 147 if( serviceName == null ) 148 { 149 if( !id.equals(NOBODY) && !isPiped ) // pipe in multiple commands one line at a time 150 { 151 final BufferedReader in = new BufferedReader( new InputStreamReader( 152 System.in, "UTF-8" )); 153 try 154 { 155 for( ;; ) 156 { 157 final String s = in.readLine(); 158 if( s == null ) return; 159 160 final String[] argvPiped; 161 { 162 final ArrayList<String> argList = 163 new ArrayList<String>( /*initial capacity*/8 ); 164 final Matcher m = COMMAND_ARGUMENT_PATTERN.matcher( s ); 165 while( m.find() ) argList.add( m.group( 1 )); 166 167 argvPiped = new String[argList.size()]; 168 argList.toArray( argvPiped ); 169 } 170 execute( argvPiped, new HashMap<String,Option>(optionMap) ); 171 // carrying over the basic command-line options to each 172 // command execution 173 } 174 } 175 finally{ in.close(); } 176 } 177 else 178 { 179 System.err.println( "voter: missing SERVICE-NAME argument" ); 180 System.err.println( GetoptX.createHelpPrompt( "voter" )); 181 System.exit( 1 ); 182 } 183 } 184 else // single command 185 { 186 final VOTer voTer = new VOTer( id, serviceName, argvCommand ); 187 voTer.run(); 188 } 189 } 190 catch( RuntimeException x ) { throw x; } 191 catch( final Exception x ) // various per VOTer.run 192 { 193 System.err.print( "voter: fatal error" + Votorola.unmessagedDetails(x) + ": " ); 194 // System.err.println( x ); 195 x.printStackTrace( System.err ); 196 System.exit( 1 ); 197 } 198 } 199 200 201 202 private VOTer( IDPair _id, String _serviceName, String[] _argvCommand ) 203 throws java.io.IOException, ScriptException, SQLException, java.net.URISyntaxException 204 { 205 id = _id; 206 serviceName = _serviceName; 207 argvCommand = _argvCommand; 208 209 if( vsRun == null ) 210 { 211 vsRun = new VoteServer( System.getProperty( "user.name" )).new Run( 212 /*isSingleThreaded*/true ); 213 vsRun.singleServiceLock().lock(); // no need to unlock, single access 214 } 215 } 216 217 218 219 // - V o t e r - I n t e r f a c e ---------------------------------------------------- 220 221 222 /** @return the <code>voter</code> command to access the service from the command line. 223 */ 224 public String serviceAccessDescriptor( final VoterService s ) 225 { 226 return "$ voter " + s.name() + " help"; 227 } 228 229 230 231//// P r i v a t e /////////////////////////////////////////////////////////////////////// 232 233 234 private final String[] argvCommand; 235 236 237 238 private VoterService ensureVoterMetaService( final String metaServiceName ) 239 throws IOException, ScriptException, SQLException 240 { 241 assert metaServiceName.equals( "mail" ); 242 return vsRun.init_ensureVoterService( 243 new File( vsRun.voteServer().votorolaDirectory(), 244 "mail" + File.separator + "mail-meta-service.js" ), 245 MailMetaService.class ); 246 } 247 248 249 250 private VoterService ensureVoterService( final String serviceName ) 251 throws IOException, ScriptException, SQLException 252 { 253 // Trustserver 254 // --------------------------------------------------------------------------------- 255 if( serviceName.equals( vsRun.trustserver().name() )) return vsRun.trustserver(); 256 257 // Meta 258 // --------------------------------------------------------------------------------- 259 final String metaServiceName = "mail"; 260 if( serviceName.equals( metaServiceName )) return ensureVoterMetaService( metaServiceName ); 261 262 // Poll (assumed) 263 // --------------------------------------------------------------------------------- 264 return vsRun.scopePoll().ensurePoll( serviceName ); 265 } 266 267 268 269 private final IDPair id; 270 271 272 273 private void run() throws Exception // ScriptException, IOException, SQLException, 274 { // URISyntaxException and soft Exception from dispatch() 275 if( argvCommand.length == 0 ) 276 { 277 System.err.println( "voter: missing COMMAND argument" ); 278 System.err.println( GetoptX.createHelpPrompt( "voter" )); 279 System.exit( 1 ); 280 } 281 282 VoterService voterService = null; 283 try 284 { 285 voterService = ensureVoterService( serviceName ); 286 } 287 catch( VoterService.NoSuchServiceException x ) 288 { 289 System.err.println( "voter: unrecognized SERVICE-NAME argument '" + serviceName + "'" ); 290 System.err.println( GetoptX.createHelpPrompt( "voter" )); 291 System.exit( 1 ); 292 } 293 294 final ReplyBuilder replyB = new ReplyBuilder( Locale.getDefault() ); 295 final BundleFormatter bunA = new BundleFormatter( ResourceBundle.getBundle( 296 "votorola.a.locale.A", replyB.locale(), new BundleControlU() )); 297 int trustLevel = 0; 298 if( !id.equals(NOBODY) ) 299 { 300 trustLevel = vsRun.trustserver().getTraceNode( /*list ref*/null, id ).trustLevel(); 301 } 302 final CommandResponder.Session commandSession = new CommandResponder.Session( VOTer.this, 303 id.email(), trustLevel, bunA, replyB ); 304 try 305 { 306 final Exception x = voterService.dispatch( argvCommand, commandSession ); 307 if( x != null ) throw x; 308 } 309 catch( CommandResponder.AnonymousIssueException x ) 310 { 311 System.err.println( "voter: missing VOTER-EMAIL (" + x.getMessage() + ")" ); 312 System.err.println( GetoptX.createHelpPrompt( "voter" )); 313 System.exit( 1 ); 314 } 315 316 System.out.print( replyB.chomplnn().toString() ); 317 System.out.flush(); 318 } 319 320 321 322 private final String serviceName; 323 324 325 326 private static VoteServer.Run vsRun; // final after init 327 328 329}