001package 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.
002
003import java.io.*;
004import java.sql.*;
005import javax.script.*;
006import javax.xml.stream.*;
007import votorola.a.*;
008import votorola.a.voter.*;
009import votorola.g.*;
010import votorola.g.lang.*;
011import votorola.g.hold.*;
012import votorola.g.io.*;
013import votorola.g.logging.*;
014import votorola.g.sql.*;
015import votorola.g.xml.stream.*;
016
017
018/** The path to a snap/readyTrace record.  It is the file part of the backing for a traced
019  * trust network and a compiled registration list.  It is guaranteed to be in canonical
020  * form at the time of construction.
021  *
022  *     @see <a href='../../../../../s/manual.xht#line-votrace'>snap/readyTrace record</a>
023  *     @see NetworkTrace
024  */
025public @ThreadSafe final class ReadyDirectory extends OutputStore.ReadyDirectory
026{
027
028    // cf. a/count/ReadyDirectory
029
030
031    private static final long serialVersionUID = 1L;
032
033
034
035    /** Creates a ready directory and returns an instance of it.
036      *
037      *     @param snapDirectory the parent directory in which to create the new ready
038      *       directory.
039      */
040    public static ReadyDirectory createReadyDirectory( final File snapDirectory ) throws IOException
041    {
042        final File d = OutputStore.mkdirsS( snapDirectory, "readyTrace" );
043          // no admin to store yet, so that's it
044        return new ReadyDirectory( d.getPath() );
045    }
046
047
048
049    /** Constructs a ReadyDirectory from an abstract (File) pathname.
050      *
051      *     @param d the abstract pathname.  It will be converted to canonical form if
052      *       necessary.
053      *
054      *     @throws FileNotFoundException if no directory exists at the specified pathname.
055      *     @see #createReadyDirectory(File)
056      */
057    public ReadyDirectory( File d ) throws IOException { super( d ); }
058
059
060
061    /** Constructs a ReadyDirectory from a string pathname.
062      *
063      *     @param pathname the string pathname, per {@linkplain File#File(String)
064      *       File}(pathname).  It will be converted to canonical form if necessary.
065      *
066      *     @throws FileNotFoundException if no directory exists at the specified pathname.
067      *     @see #createReadyDirectory(File)
068      */
069    public ReadyDirectory( String pathname ) throws IOException { super( new File( pathname )); }
070
071
072
073   // ------------------------------------------------------------------------------------
074
075
076    /** The file storing the snapshot of registrant input from the streetwiki, in XML
077      * format.
078      */
079    File inRegistrationFile() { return inRegistrationFile; }
080
081
082        private final File inRegistrationFile = new File( snapDirectory(), "_in_registration.xml" );
083
084
085
086    /** Traces the trust network, compiles the registration list, and posts the results to
087      * the database and filebase.
088      *
089      *     @see #isMounted()
090      *     @see #unmount(VoteServer.Run)
091      */
092    public NetworkTrace mount( final Trustserver trustserver, final boolean isVerbose )
093      throws javax.mail.internet.AddressException, IOException, ScriptException, SQLException,
094        XMLStreamException
095    {
096        final Database database = trustserver.vsRun().database();
097        final Membership.Table membershipTable = new Membership.Table( ReadyDirectory.this, database );
098        membershipTable.drop();
099        membershipTable.create();
100        final TraceNodeW.Table traceNodeTable = new TraceNodeW.Table( ReadyDirectory.this, database );
101        traceNodeTable.drop();
102        traceNodeTable.create();
103        final TrustEdge.Table trustEdgeTable = new TrustEdge.Table( ReadyDirectory.this, database );
104        trustEdgeTable.drop();
105        trustEdgeTable.create();
106        synchronized( database ){ database.connection().setAutoCommit( false ); } // bulk write
107        try
108        {
109
110          // 1. Read snapshot of registrant input, create trace nodes, divisional memberships
111          //    and trust edges
112          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113            if( isVerbose ) System.out.print( "1" );
114              // Pre-creation of edges records the skeletal structure of the trust network in relational
115              // form, so it need not be repeated in each trace node.  Pre-creation of trace nodes
116              // allows for subsequent lookup of edge sides (source and destination node) in pass 2, so
117              // the edge bars can be recorded there, prior to attempting the extension of edges.
118              // (While we're at it, we pre-create the memberships too.)
119            {
120                final InputStream in = new BufferedInputStream( new FileInputStream( inRegistrationFile ));
121                try
122                {
123                    final XMLStreamReader r;
124                    synchronized( XMLInputFactoryX.class )
125                    {
126                        r = XMLInputFactoryX.SIMPLE_INPUT_FACTORY.createXMLStreamReader(
127                          /*systemId*/inRegistrationFile.toString(), in );
128                    }
129                    try
130                    {
131                        while( r.hasNext() )
132                        {
133                            r.next();
134                            if( r.isStartElement() && "in".equals( r.getLocalName() ))
135                            {
136                                final Registration reg = new Registration( r );
137                                final IDPair registrant = reg.registrant();
138                                for( final String div: reg.divisions() )
139                                {
140                                    final Membership membership = new Membership( registrant, div );
141                                    membership.write( membershipTable );
142                                }
143                                final TraceNodeW node = new TraceNodeW( registrant, reg.getArea(),
144                                  reg.getCountryCode(), reg.getGeohandle(), reg.getOtherProperties() );
145                                node.write( traceNodeTable );
146                                for( final IDPair registrant0: reg.trusters() )
147                                {
148                                    final TrustEdge edge = new TrustEdge( registrant0, registrant );
149                                    edge.write( trustEdgeTable );
150                                }
151                            }
152                        }
153                    }
154                    finally{ r.close(); }
155                }
156                finally{ in.close(); }
157            }
158
159          // 2. For each trust edge, record any bar
160          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
161            if( isVerbose ) System.out.print( "2" );
162            trustEdgeTable.init_bars( trustserver, traceNodeTable );
163
164          // 3. Inject primary edges into the trust network and throughout
165          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
166            if( isVerbose ) System.out.print( "3" );
167            final NetworkTracer tracer = new NetworkTracer( traceNodeTable, trustEdgeTable );
168            for( TrustEdge.Primary primary: trustserver.primaryTrustList() )
169            {
170                LoggerX.i(getClass()).config( "injecting primary trust edge(s) to registant " + primary.registrant1() );
171                final TraceNodeW primaryDestination = traceNodeTable.get( primary.registrant1() );
172                if( primaryDestination == null )
173                {
174                    LoggerX.i(getClass()).warning( "cannot extend primary trust edge(s) to " + primary.registrant1() + ": no such registrant" );
175                    continue;
176                }
177
178             // assert primaryDestination.trustLevel() == 0;
179             /// why? an earlier primary could have trusted it
180                for( int e = 0, eN = primary.edgeCount(); e < eN; ++e )
181                {
182                    primaryDestination.attachTrustEdge( Integer.MAX_VALUE );
183                }
184                primaryDestination.write( traceNodeTable );
185                tracer.extendLevelChange( primaryDestination, 0 ); // throughout network
186            }
187
188          // 4. Serialize the mounted trace into registration lists
189          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
190            if( isVerbose ) System.out.print( "4" );
191            final class NodeRunner implements TraceNodeW.Runner
192            {
193                private String area = ""; // initially not a valid area, and not null
194
195                Spool spool = Spool0.i();
196
197                private BufferedWriter w = null;
198
199                public void run( final TraceNodeW node )
200                {
201                    try
202                    {
203                        if( !ObjectX.nullEquals( node.getArea(), area ))
204                        {
205                            spool.unwind();
206                            spool = new Spool1(); // if not unwound above, then farther below
207                            area = node.getArea();
208
209                            final File traceFile = newListSerialFile( area );
210                            final BufferedWriter writerNew = new BufferedWriter(
211                              new OutputStreamWriter( new FileOutputStream( traceFile ), "UTF-8" ));
212                            spool.add( new Hold()
213                            {
214                                public void release()
215                                {
216                                    try
217                                    {
218                                        writerNew.append( "\t</register>\n" );
219                                        writerNew.close();
220                                    }
221                                    catch( IOException x ) { throw new VotorolaRuntimeException( x ); }
222                                }
223                            });
224                            w = writerNew;
225                            w.append( "<?xml version='1.0' encoding='UTF-8'?> <!-- -*-coding: utf-8;-*- -->\n" );
226                            w.append( "<register" );
227                            if( area != null ) w.append( " area='" + area + "'" );
228
229                            w.append( ">\n" );
230                        }
231
232                        node.toXML( membershipTable.divisionSet(node.registrant().email()), w );
233                    }
234                    catch( IOException|SQLException x ) { throw new VotorolaRuntimeException( x ); }
235                }
236            };
237
238            mountedDirectory.mkdir();
239            final NodeRunner nodeRunner = new NodeRunner();
240            traceNodeTable.run( nodeRunner );
241            nodeRunner.spool.unwind();
242        }
243        finally{ synchronized( database ){ database.connection().setAutoCommit( true ); }}
244          // will do a commit, too
245
246      // - - -
247        final NetworkTrace trace = new NetworkTrace( ReadyDirectory.this );
248        trace.writeObjectToSerialFile();
249        if( isVerbose ) System.out.println();
250        return trace;
251    }
252
253
254    /** The directory to which the trace is serialized, when mounted.
255      */
256    public File mountedDirectory() { return mountedDirectory; }
257
258
259        private final File mountedDirectory = new File( ReadyDirectory.this, "_mountedTrace" );
260
261
262
263    /** Constructs the file to which a registration list is serialized when mounted.  Each
264      * {@linkplain TraceNode#getArea() area} has a separate list, and the list of
265      * unlocalized registrations is serialized to the file '_mountedTrace/_default.xml'.
266      */
267    public File newListSerialFile( final String area )
268    {
269        return new File( mountedDirectory(), (area == null? "_default": area) + ".xml" );
270    }
271
272
273
274    /** Constructs the file to which a network trace is serialized when mounted.
275      */
276    public File newTraceSerialFile()
277    {
278        return new File( mountedDirectory(), "_trace.serial" );
279    }
280
281
282
283    /** A file filter that accepts only apparent ready directories.
284      */
285    public static final FileFilter READY_DIRECTORY_FILTER = new FileFilter()
286    {
287        public boolean accept( final File file )
288        {
289            final String name = file.getName();
290            return file.isDirectory() && name.startsWith( "readyTrace-" )
291              && OutputStore.isS( OutputStore.suffix( name ));
292        }
293    };
294
295
296
297    /** Reverses a previous mount, erasing the results from the database and filebase.
298      *
299      *     @return true if the trace was unmounted, false if it was not mounted to begin
300      *       with, either in whole or part.
301      *
302      *     @see #isMounted()
303      *     @see #mount(Trustserver,boolean)
304      */
305    public boolean unmount( final VoteServer.Run vsRun ) throws IOException, SQLException
306    {
307        boolean unmounted = false; // thus far
308        if( mountedDirectory.isDirectory() )
309        {
310            if( !FileX.deleteRecursive( mountedDirectory )) throw new IOException( "unable to recursively delete directory: " + mountedDirectory );
311
312            unmounted = true;
313        }
314        final Database database = vsRun.database();
315        if( new Membership.Table(ReadyDirectory.this,database).drop() ) unmounted = true;
316        if( new TraceNodeW.Table(ReadyDirectory.this,database).drop() ) unmounted = true;
317        if( new TrustEdge.Table(ReadyDirectory.this,database).drop() ) unmounted = true;
318        return unmounted;
319    }
320
321
322
323   // - 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 --------------------------
324
325
326    /** Answers whether the trace is nominally mounted.
327      */
328    public boolean isMounted() { return mountedDirectory.isDirectory(); }
329
330
331
332}