package votorola.a.trust; // Copyright 2008-2010, 2012, 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 javax.script.*; import javax.xml.stream.*; import votorola.a.*; import votorola.a.voter.*; import votorola.g.*; import votorola.g.lang.*; import votorola.g.hold.*; import votorola.g.io.*; import votorola.g.logging.*; import votorola.g.sql.*; import votorola.g.xml.stream.*; /** The path to a snap/readyTrace record. It is the file part of the backing for a traced * trust network and a compiled registration list. It is guaranteed to be in canonical * form at the time of construction. * * @see snap/readyTrace record * @see NetworkTrace */ public @ThreadSafe final class ReadyDirectory extends OutputStore.ReadyDirectory { // cf. a/count/ReadyDirectory private static final long serialVersionUID = 1L; /** Creates a ready directory and returns an instance of it. * * @param snapDirectory the parent directory in which to create the new ready * directory. */ public static ReadyDirectory createReadyDirectory( final File snapDirectory ) throws IOException { final File d = OutputStore.mkdirsS( snapDirectory, "readyTrace" ); // no admin to store yet, so that's it return new ReadyDirectory( d.getPath() ); } /** Constructs a ReadyDirectory from an abstract (File) pathname. * * @param d the abstract pathname. It will be converted to canonical form if * necessary. * * @throws FileNotFoundException if no directory exists at the specified pathname. * @see #createReadyDirectory(File) */ public ReadyDirectory( File d ) throws IOException { super( d ); } /** Constructs a ReadyDirectory from a string pathname. * * @param pathname the string pathname, per {@linkplain File#File(String) * File}(pathname). It will be converted to canonical form if necessary. * * @throws FileNotFoundException if no directory exists at the specified pathname. * @see #createReadyDirectory(File) */ public ReadyDirectory( String pathname ) throws IOException { super( new File( pathname )); } // ------------------------------------------------------------------------------------ /** The file storing the snapshot of registrant input from the streetwiki, in XML * format. */ File inRegistrationFile() { return inRegistrationFile; } private final File inRegistrationFile = new File( snapDirectory(), "_in_registration.xml" ); /** Traces the trust network, compiles the registration list, and posts the results to * the database and filebase. * * @see #isMounted() * @see #unmount(VoteServer.Run) */ public NetworkTrace mount( final Trustserver trustserver, final boolean isVerbose ) throws javax.mail.internet.AddressException, IOException, ScriptException, SQLException, XMLStreamException { final Database database = trustserver.vsRun().database(); final Membership.Table membershipTable = new Membership.Table( ReadyDirectory.this, database ); membershipTable.drop(); membershipTable.create(); final TraceNodeW.Table traceNodeTable = new TraceNodeW.Table( ReadyDirectory.this, database ); traceNodeTable.drop(); traceNodeTable.create(); final TrustEdge.Table trustEdgeTable = new TrustEdge.Table( ReadyDirectory.this, database ); trustEdgeTable.drop(); trustEdgeTable.create(); synchronized( database ){ database.connection().setAutoCommit( false ); } // bulk write try { // 1. Read snapshot of registrant input, create trace nodes, divisional memberships // and trust edges // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( isVerbose ) System.out.print( "1" ); // Pre-creation of edges records the skeletal structure of the trust network in relational // form, so it need not be repeated in each trace node. Pre-creation of trace 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. // (While we're at it, we pre-create the memberships too.) { final InputStream in = new BufferedInputStream( new FileInputStream( inRegistrationFile )); try { final XMLStreamReader r; synchronized( XMLInputFactoryX.class ) { r = XMLInputFactoryX.SIMPLE_INPUT_FACTORY.createXMLStreamReader( /*systemId*/inRegistrationFile.toString(), in ); } try { while( r.hasNext() ) { r.next(); if( r.isStartElement() && "in".equals( r.getLocalName() )) { final Registration reg = new Registration( r ); final IDPair registrant = reg.registrant(); for( final String div: reg.divisions() ) { final Membership membership = new Membership( registrant, div ); membership.write( membershipTable ); } final TraceNodeW node = new TraceNodeW( registrant, reg.getArea(), reg.getCountryCode(), reg.getGeohandle(), reg.getOtherProperties() ); node.write( traceNodeTable ); for( final IDPair registrant0: reg.trusters() ) { final TrustEdge edge = new TrustEdge( registrant0, registrant ); edge.write( trustEdgeTable ); } } } } finally{ r.close(); } } finally{ in.close(); } } // 2. For each trust edge, record any bar // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( isVerbose ) System.out.print( "2" ); trustEdgeTable.init_bars( trustserver, traceNodeTable ); // 3. Inject primary edges into the trust network and throughout // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( isVerbose ) System.out.print( "3" ); final NetworkTracer tracer = new NetworkTracer( traceNodeTable, trustEdgeTable ); for( TrustEdge.Primary primary: trustserver.primaryTrustList() ) { LoggerX.i(getClass()).config( "injecting primary trust edge(s) to registant " + primary.registrant1() ); final TraceNodeW primaryDestination = traceNodeTable.get( primary.registrant1() ); if( primaryDestination == null ) { LoggerX.i(getClass()).warning( "cannot extend primary trust edge(s) to " + primary.registrant1() + ": no such registrant" ); continue; } // assert primaryDestination.trustLevel() == 0; /// why? an earlier primary could have trusted it for( int e = 0, eN = primary.edgeCount(); e < eN; ++e ) { primaryDestination.attachTrustEdge( Integer.MAX_VALUE ); } primaryDestination.write( traceNodeTable ); tracer.extendLevelChange( primaryDestination, 0 ); // throughout network } // 4. Serialize the mounted trace into registration lists // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( isVerbose ) System.out.print( "4" ); final class NodeRunner implements TraceNodeW.Runner { private String area = ""; // initially not a valid area, and not null Spool spool = Spool0.i(); private BufferedWriter w = null; public void run( final TraceNodeW node ) { try { if( !ObjectX.nullEquals( node.getArea(), area )) { spool.unwind(); spool = new Spool1(); // if not unwound above, then farther below area = node.getArea(); final File traceFile = newListSerialFile( area ); final BufferedWriter writerNew = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( traceFile ), "UTF-8" )); spool.add( new Hold() { public void release() { try { writerNew.append( "\t\n" ); writerNew.close(); } catch( IOException x ) { throw new VotorolaRuntimeException( x ); } } }); w = writerNew; w.append( " \n" ); w.append( "\n" ); } node.toXML( membershipTable.divisionSet(node.registrant().email()), w ); } catch( IOException|SQLException x ) { throw new VotorolaRuntimeException( x ); } } }; mountedDirectory.mkdir(); final NodeRunner nodeRunner = new NodeRunner(); traceNodeTable.run( nodeRunner ); nodeRunner.spool.unwind(); } finally{ synchronized( database ){ database.connection().setAutoCommit( true ); }} // will do a commit, too // - - - final NetworkTrace trace = new NetworkTrace( ReadyDirectory.this ); trace.writeObjectToSerialFile(); if( isVerbose ) System.out.println(); return trace; } /** The directory to which the trace is serialized, when mounted. */ public File mountedDirectory() { return mountedDirectory; } private final File mountedDirectory = new File( ReadyDirectory.this, "_mountedTrace" ); /** Constructs the file to which a registration list is serialized when mounted. Each * {@linkplain TraceNode#getArea() area} has a separate list, and the list of * unlocalized registrations is serialized to the file '_mountedTrace/_default.xml'. */ public File newListSerialFile( final String area ) { return new File( mountedDirectory(), (area == null? "_default": area) + ".xml" ); } /** Constructs the file to which a network trace is serialized when mounted. */ public File newTraceSerialFile() { return new File( mountedDirectory(), "_trace.serial" ); } /** A file filter that accepts only apparent ready directories. */ public static final FileFilter READY_DIRECTORY_FILTER = new FileFilter() { public boolean accept( final File file ) { final String name = file.getName(); return file.isDirectory() && name.startsWith( "readyTrace-" ) && OutputStore.isS( OutputStore.suffix( name )); } }; /** Reverses a previous mount, erasing the results from the database and filebase. * * @return true if the trace was unmounted, false if it was not mounted to begin * with, either in whole or part. * * @see #isMounted() * @see #mount(Trustserver,boolean) */ public boolean unmount( final VoteServer.Run vsRun ) throws IOException, SQLException { boolean unmounted = false; // thus far if( mountedDirectory.isDirectory() ) { if( !FileX.deleteRecursive( mountedDirectory )) throw new IOException( "unable to recursively delete directory: " + mountedDirectory ); unmounted = true; } final Database database = vsRun.database(); if( new Membership.Table(ReadyDirectory.this,database).drop() ) unmounted = true; if( new TraceNodeW.Table(ReadyDirectory.this,database).drop() ) unmounted = true; if( new TrustEdge.Table(ReadyDirectory.this,database).drop() ) unmounted = true; return unmounted; } // - O u t p u t - S t o r e . R e a d y - D i r e c t o r y -------------------------- /** Answers whether the trace is nominally mounted. */ public boolean isMounted() { return mountedDirectory.isDirectory(); } }