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}