package votorola.a.register; // Copyright 2008, 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 javax.script.*; import javax.xml.stream.*; import votorola._.*; import votorola.a.*; import votorola.a.manline.*; import votorola.a.register.trust.*; import votorola.g.lang.*; import votorola.g.logging.*; import votorola.g.sql.*; /** The file part of the backing to a {@linkplain VoterList voter list}. * Its path is always in canonical form. */ public @ThreadSafe final class ReadyDirectory extends File { // Cf. a/election/ReadyDirectory /** Creates a ready directory, and returns an instance of it. * * @param snapDirectory parent directory in which to create * the new ready directory */ static ReadyDirectory createReadyDirectory( File snapDirectory ) throws IOException { File directory = VOSnap.mkdirsDateSuffixed( snapDirectory, "readyList" ); // no admin to store yet, so that's it return new ReadyDirectory( directory.getPath() ); } /** Constructs a ReadyDirectory. * * @param pathname per {@linkplain File#File(String) File}(pathname), * (will be converted to canonical form, if it is non-canonical) * * @throws FileNotFoundException if no directory exists at the specified pathname * @see #createReadyDirectory(File) */ public ReadyDirectory( String pathname ) throws IOException { super( new File(pathname).getCanonicalPath() ); if( !isDirectory() ) throw new FileNotFoundException( pathname ); } // ```````````````````````````````````````````````````````````````````````````````````` // Initialized early, for use in other initializers. private final File snapDirectory = getParentFile(); // ------------------------------------------------------------------------------------ /** Returns the file storing the snapshot of voter input. */ public File inVoterFile() { return inVoterFile; } private final File inVoterFile = new File( snapDirectory, "_inVoter.txt" ); /** Returns true if the list is nominally mounted; false otherwise. */ public boolean isMounted() { return mountedFile.isFile(); } /** Tallies the results and makes them available via the database and filebase. * * @param register a register service corresponding to * this ready directory * @return mounted list * * @see #isMounted() * @see #unmount(Register) */ VoterList mount( final Register register ) throws IOException, ScriptException, SQLException { ElectoralService.ensureSchema( register.listDatabase(), register.name() ); final StringBuilder stringB = new StringBuilder( /*initial capacity, guess*/200 ); final ListNodeC.Table listNodeTable = new ListNodeC.Table( ReadyDirectory.this, register.listDatabase() ); if( listNodeTable.exists() ) listNodeTable.drop(); listNodeTable.create(); final TrustEdge.Table trustEdgeTable = new TrustEdge.Table( ReadyDirectory.this, register.listDatabase() ); if( trustEdgeTable.exists() ) trustEdgeTable.drop(); trustEdgeTable.create(); // Pass 1. Read snapshot of voter input, and create list nodes and trust edges. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Pre-creation of edges records the skeletal structure of the trust network // in relational form, so it need not be repeated in each list node. // Pre-creation of list nodes allows for subsequent lookup of edge sides // (source and destination node) in pass 2, so the edge bars can be recorded there, // prior to attempting the extension of edges. // final BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( inVoterFile ), "UTF-8" )); try { for( ;; ) { final String voter0Email = readVoterInputColumn( stringB, reader, false ); if( voter0Email == null ) break; final ListNodeC node = new ListNodeC( listNodeTable, voter0Email ); final String xml = readVoterInputColumn( stringB, reader, true ); // finish reading the line try { final RegistrationC voterInput = new RegistrationC( voter0Email, xml ); node.setResidence( voterInput.getResidence() ); node.commit(); for( String voter1Email: voterInput.trustDestinations() ) { TrustEdge edge = new TrustEdge( voter0Email, voter1Email ); edge.commit( trustEdgeTable ); } } catch( XMLStreamException x ) { throw new VotorolaRuntimeException( "unparseable data from file " + inVoterFile + ", for voterEmail " + voter0Email + ": " + xml, x ); } } } finally{ reader.close(); } // Pass 2. For each trust edge, record any extension bar. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - trustEdgeTable.init_bars( register, listNodeTable ); // Pass 3. Extend primary edges into the trust network, and throughout. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final TrustTrace trace = new TrustTrace( trustEdgeTable ); for( TrustEdge.Primary primary: register.primaryTrustList() ) { LoggerX.i(getClass()).config( "extending primary trust edge(s) to voter " + primary.voter1Email() ); final ListNodeC primaryDestination = listNodeTable.get( primary.voter1Email() ); if( primaryDestination == null ) { LoggerX.i(getClass()).warning( "cannot extend primary trust edge(s) to voter " + primary.voter1Email() + ": voter not registered" ); continue; } assert primaryDestination.trustLevel() == 0; for( int e = 0, eN = primary.edgeCount(); e < eN; ++e ) { primaryDestination.attachTrustEdge( Integer.MAX_VALUE ); } primaryDestination.commit(); trace.extendLevelChange( primaryDestination, 0 ); // througout network } // Pass 4. Index the list nodes. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // This initializes them, complete except doubtCount (which depends // on bars being set). // listNodeTable.run( new ListNodeC.Runner() { private final ListIndexingContext li = new ListIndexingContext(); public void run( final ListNodeC node ) { try { register.listScript().invokeKnownFunction( "indexing", li.set( node )); node.commit(); } catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } } }); // Pass 5. Count the unbarred doubters. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - register.doubtTable().runFrom( listNodeTable, new DoubtSignalNode.Runner() { public void run( final DoubtSignalNode signalNodeFrom ) { if( signalNodeFrom.listNode().getBar() != null ) return; try { final ListNodeC destinationListNode = listNodeTable.get( signalNodeFrom.doubtSignal().voter1Email() ); if( destinationListNode == null ) return; destinationListNode.incrementDoubterCount(); destinationListNode.commit(); } catch( SQLException x ) { throw new VotorolaRuntimeException( x ); } } }); // Pass 6. Build the neighbourhood table. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final Neighbourhood.CachedTable neighbourhoodTable = new Neighbourhood.CachedTable( ReadyDirectory.this, register.listDatabase() ); if( neighbourhoodTable.exists() ) neighbourhoodTable.drop(); neighbourhoodTable.create(); listNodeTable.run( new ListNodeC.Runner() { public void run( final ListNodeC node ) { try { neighbourhoodTable.addVoter( node ); } catch( SQLException x ) { throw new VotorolaRuntimeException( x ); } } }); neighbourhoodTable.commitAll(); // - - - final VoterList list = new VoterList( register, ReadyDirectory.this ); list.writeObjectToMountedListFile(); return list; } /** Returns the file to which the list is serialized, when mounted. */ public File mountedFile() { return mountedFile; } private final File mountedFile = new File( ReadyDirectory.this, "_mountedList.serial" ); /** Returns the {@linkplain ElectoralService#name() service identifier}, * obtained as the name of the grandparent file. */ public String name() { return snapDirectory.getParentFile().getName(); } // FIX misnomer, implies directory has this name /** Reads a single column value. * * @param stringB to use, after clearing it * * @return value, or null if input exhausted */ public String readVoterInputColumn( final StringBuilder stringB, final Reader in, final boolean isLineTerminator ) throws IOException { final char validDelimiter; final char invalidDelimiter; if( isLineTerminator ) { validDelimiter = '\n'; invalidDelimiter = '\t'; } else { validDelimiter = '\t'; invalidDelimiter = '\n'; } stringB.delete( 0, Integer.MAX_VALUE ); boolean isEOF = false; // so far for( ;; ) { int ich = in.read(); isEOF = ich == -1; if( isEOF ) break; char ch = (char)ich; if( ch == validDelimiter ) break; else if( ch == invalidDelimiter ) throw new IOException( "corrupt voter input file: " + inVoterFile ); stringB.append( ch ); } if( stringB.length() == 0 ) { if( !isEOF ) throw new IOException( "empty value in voter input file: " + inVoterFile ); if( isLineTerminator ) throw new IOException( "missing value, unexpected EOF in file: " + inVoterFile ); return null; } else return stringB.toString(); } /** Returns the parent snap directory. */ public File snapDirectory() { return snapDirectory; } /** Returns a short identifier intended for presenting to users. It includes the * names of the parent snap directory, and this ready directory. */ public String toUIString() { // final File serviceDirectory = snapDirectory.getParentFile(); return // serviceDirectory.getName() + " / " + snapDirectory.getName() + " / " + getName(); } /** Reverses a previous mount, erasing the results from the database and filebase. * * @param register a register service corresponding to * this ready directory * @return true if the list was unmounted, * false if it was not mounted to begin with, either in whole or part * * @see #isMounted() * @see #mount(Register) */ boolean unmount( final Register register ) throws IOException, SQLException { boolean unmounted = false; // thus far if( mountedFile.isFile() ) { if( !mountedFile.delete() ) throw new IOException( "unable to delete file: " + mountedFile ); unmounted = true; } final ListNodeC.Table listNodeTable = new ListNodeC.Table( ReadyDirectory.this, register.listDatabase() ); if( listNodeTable.exists() ) { listNodeTable.drop(); unmounted = true; } final Neighbourhood.Table neighbourhoodTable = new Neighbourhood.Table( ReadyDirectory.this, register.listDatabase() ); if( neighbourhoodTable.exists() ) { neighbourhoodTable.drop(); unmounted = true; } final TrustEdge.Table trustEdgeTable = new TrustEdge.Table( ReadyDirectory.this, register.listDatabase() ); if( trustEdgeTable.exists() ) { trustEdgeTable.drop(); unmounted = true; } return unmounted; } }