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 java.util.*;
006import javax.script.*;
007import votorola.a.*;
008import votorola.a.voter.*;
009import votorola.g.*;
010import votorola.g.lang.*;
011import votorola.g.sql.*;
012
013
014/** A formal expression of trust that extends from one registrant (source) to another
015  * (destination) in a trust network.
016  *
017  *     @see <a href='../../../../../../s/manual.xht#Trust'
018  *                           >../../../s/manual.xht#Trust</a>
019  *     @see TraceNode
020  */
021public final class TrustEdge
022{
023
024
025    /** Creates a TrustEdge with default values.
026      */
027    public TrustEdge( IDPair _registrant0, IDPair _registrant1 )
028    {
029        if( _registrant0 == null || _registrant1 == null ) throw new NullPointerException(); // fail fast
030
031        registrant0 = _registrant0;
032        registrant1 = _registrant1;
033        isChanged = true;
034    }
035
036
037
038    /** Constructs a TrustEdge.
039      */
040    TrustEdge( IDPair _registrant0, IDPair _registrant1, String bar )
041    {
042        if( _registrant0 == null || _registrant1 == null ) throw new NullPointerException(); // fail fast
043
044        // Adding fields here?  Increment NetworkTrace.serialVersionUID.
045        registrant0 = _registrant0;
046        registrant1 = _registrant1;
047        bar = bar;
048    }
049
050
051
052   // ------------------------------------------------------------------------------------
053
054
055    /** Describes the bar against the trust edge, if any has been set.  The description is
056      * intended for users to read.  It should include details that may be of help in
057      * overcoming the bar.  For instance, if the edge is barred because of a problem with
058      * a particular registration property (geohandle for instance), then the bar should
059      * point out the exact problem with that property (the specific syntax error, or
060      * whatever).
061      *
062      * <p>A barred trust edge has no effect on the trust network.  It extends no
063      * trust.</p>
064      *
065      *     @return description of bar, or null if no bar is set
066      *
067      *     @see #setBar(String)
068      */
069    public String getBar() { return bar; }
070
071
072        private String bar;
073
074
075        /** Sets a bar against the edge.
076          *
077          *     @see #getBar()
078          */
079        public void setBar( String newBar )
080        {
081            if( ObjectX.nullEquals( newBar, bar )) return;
082
083            bar = newBar;
084            isChanged = true;
085        }
086
087
088
089    /** The trust source.
090      */
091    public IDPair registrant0() { return registrant0; }
092
093
094        private final IDPair registrant0;
095
096
097
098    /** The trust destination.
099      */
100    public IDPair registrant1() { return registrant1; }
101
102
103        private final IDPair registrant1;
104
105
106
107    /** Writes this edge to the table if it has unwritten changes.
108      */
109    public void write( final Table table ) throws SQLException
110    {
111        if( !isChanged ) return;
112
113        table.put( TrustEdge.this );
114        isChanged = false;
115    }
116
117
118
119   // - O b j e c t ----------------------------------------------------------------------
120
121
122    /** Constructs a description of the edge, including the names of both registrants.
123      */
124    public @Override String toString()
125    {
126        return "TrustEdge[" + registrant0() + "," + registrant1() + "]";
127    }
128
129
130
131   // ====================================================================================
132
133
134    /** The extension of {@linkplain TrustEdge trust edges} to a primary destination.  The
135      * source of the extension is always the root (registrar).  Unlike an ordinary
136      * extension, the source may extend any number of edges to the primary destination.
137      *
138      *     @see <a href='../../../../../../s/manual.xht#primary-trust-edge'
139      *                           >../../../s/manual.xht#primary-trust-edge</a>
140      */
141    public static @ThreadSafe final class Primary
142    {
143
144
145        /** Constructs a Primary.
146          */
147        public Primary( IDPair _registrant1, int _edgeCount )
148        {
149            registrant1 = _registrant1;
150            edgeCount = _edgeCount;
151        }
152
153
154
155        /** The number of edges extended to the primary destination.
156          */
157        public int edgeCount() { return edgeCount; }
158
159
160            private final int edgeCount;
161
162
163
164        /** The destination of the primary edge.
165          */
166        public IDPair registrant1() { return registrant1; }
167
168
169            private final IDPair registrant1;
170
171
172
173    }
174
175
176
177   // ====================================================================================
178
179
180    /** The relational store of trust edges that (in part) backs a compiled network trace.
181      *
182      *     @see votorola.a.trust.TraceNodeW.Table
183      */
184    public static @ThreadSafe final class Table
185    {
186
187
188        /** Constructs a Table.
189          */
190        public Table( final ReadyDirectory readyDirectory, final Database database )
191          throws IOException, SQLException
192        {
193            this.readyDirectory = readyDirectory;
194            this.database = database;
195
196            synchronized( database ) { database.ensureSchema( SCHEMA_NAME ); }
197            final String snapSuffix = OutputStore.suffix(
198              readyDirectory.snapDirectory().getName() );
199            if( !OutputStore.isY4MDS( snapSuffix )) throw new VotorolaRuntimeException( "improperly suffixed snap directory parent of ready directory: " + readyDirectory );
200
201            tableName = snapSuffix.substring(1) + OutputStore.SUFFIX_DELIMITER
202              + "trust_edge" + OutputStore.suffix(readyDirectory.getCanonicalFile().getName());
203            statementKeyBase = getClass().getName() + ":" + SCHEMA_NAME + "/" + tableName + ".";
204        }
205
206
207
208        /** For each trust edge in the table, calculate and store its edge bar, if any.
209          *
210          *     @param nodeTable containing all registered users
211          */
212        public void init_bars( final Trustserver trustserver, final TraceNodeW.Table nodeTable )
213          throws ScriptException, SQLException
214        {
215            final String sKey = statementKeyBase + "init_bars";
216            synchronized( database )
217            {
218                PreparedStatement s = database.statementCache().get( sKey );
219                if( s == null )
220                {
221                    s = database.connection().prepareStatement(
222                      "SELECT registrant0Email,registrant1Email,bar"
223                      + " FROM \"" + SCHEMA_NAME + "\".\"" + tableName + "\""
224                      + " ORDER BY registrant0Email" ); // ordered to allow reuse of node0, below (maybe FIX to use TraceNodeW.NodeRunner)
225                    database.statementCache().put( sKey, s );
226                }
227                final ResultSet r = s.executeQuery();
228                try
229                {
230                    TraceNodeW node0 = null; // to reuse for efficiency, per 'ORDER BY' above
231                    while( r.next() )
232                    {
233                        final TrustEdge edge = new TrustEdge( IDPair.fromEmail(r.getString(1)),
234                          IDPair.fromEmail(r.getString(2)), r.getString(3) );
235                        if( node0 == null || !node0.registrant().equalsEmail( edge.registrant0() ))
236                        {
237                            node0 = nodeTable.get( edge.registrant0() );
238                            if( node0 == null )
239                            {
240                                assert false: "each truster is registered in nodeTable";
241                                  // maybe a stale, deleted page in streetwiki cache
242                                continue;
243                            }
244                        }
245
246                        final String bar;
247                        final TraceNodeW node1 = nodeTable.get( edge.registrant1() );
248                        if( node1 == null )
249                        {
250                            edge.setBar( (String)
251                              trustserver.runtimeConfigurationScript().invokeKnownFunction(
252                                "trustBarUnregistered", edge.registrant1() ));
253                        }
254                        else
255                        {
256                            trustserver.runtimeConfigurationScript().invokeKnownFunction(
257                              "extendingTrust", new TrustExtensionContext(
258                                trustserver, node0, node1, edge ));
259                        }
260                        edge.write( Table.this ); // if changed
261                    }
262                }
263                finally{ r.close(); }
264            }
265        }
266
267
268
269        private final String statementKeyBase;
270
271
272
273        private final String tableName;
274
275
276
277       // --------------------------------------------------------------------------------
278
279
280        /** Creates this table in the database.
281          */
282        public void create() throws SQLException
283        {
284            final String sKey = statementKeyBase + "create";
285            synchronized( database )
286            {
287                PreparedStatement s = database.statementCache().get( sKey );
288                if( s == null )
289                {
290                    s = database.connection().prepareStatement(
291                     "CREATE TABLE \"" + SCHEMA_NAME + "\".\"" + tableName + "\""
292                      + " (registrant0Email character varying,"
293                      +  " registrant1Email character varying,"
294                      +  " bar character varying,"
295                      +  " PRIMARY KEY (registrant0Email, registrant1Email))" );
296
297                    // Changing table structure?  Then also increment NetworkTrace.serialVersionUID.
298
299                    database.statementCache().put( sKey, s );
300                }
301                s.execute();
302            }
303        }
304
305
306
307        /** The database in which this table is stored.
308          */
309        @Warning("thread restricted object") Database database() { return database; }
310
311
312            private final Database database;
313
314
315
316        /** Drops this table from the database if it exists.
317          *
318          *     @return true if any rows were actually removed as a result, false otherwise.
319          */
320        public final boolean drop() throws SQLException
321        {
322            final String sKey = statementKeyBase + "drop";
323            synchronized( database )
324            {
325                PreparedStatement s = database.statementCache().get( sKey );
326                if( s == null )
327                {
328                    s = database.connection().prepareStatement(
329                     "DROP TABLE IF EXISTS \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" );
330                    database.statementCache().put( sKey, s );
331                }
332                final int updatedRows = s.executeUpdate();
333                return updatedRows > 0;
334            }
335        }
336
337
338
339        /** Retrieves all edges that extend from the specified source registrant.
340          */
341        List<TrustEdge> listEdgesFrom( final IDPair registrant0 ) throws SQLException
342        {
343            if( registrant0 == null ) throw new NullPointerException(); // fail fast
344
345            final String sKey = statementKeyBase + "listEdgesFrom";
346            synchronized( database )
347            {
348                PreparedStatement s = database.statementCache().get( sKey );
349                if( s == null )
350                {
351                    s = database.connection().prepareStatement(
352                      "SELECT registrant1Email,bar"
353                      + " FROM \"" + SCHEMA_NAME + "\".\"" + tableName + "\""
354                      + " WHERE registrant0Email = ?" );
355                    database.statementCache().put( sKey, s );
356                }
357                s.setString( 1, registrant0.email() );
358                final ResultSet r = s.executeQuery();
359                try
360                {
361                    final ArrayList<TrustEdge> edgeList = new ArrayList<TrustEdge>();
362                    while( r.next() )
363                    {
364                        edgeList.add( new TrustEdge( registrant0, IDPair.fromEmail(r.getString(1)),
365                          r.getString(2) ));
366                    }
367                    return edgeList;
368                }
369                finally{ r.close(); }
370            }
371        }
372
373
374
375        /** Stores an edge in this table.
376          */
377        void put( final TrustEdge edge ) throws SQLException
378        {
379            final String qTable = "\"" + SCHEMA_NAME + "\".\"" + tableName + "\"";
380            synchronized( database )
381            {
382                // effect an "upsert" in PostgreSQL
383                // http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql/6527838#6527838
384                final Connection c = database.connection();
385                {
386                    final String sKey = statementKeyBase + "putU";
387                    PreparedStatement s = database.statementCache().get( sKey );
388                    if( s == null )
389                    {
390                        s = c.prepareStatement( "UPDATE " + qTable
391                          + " SET bar = ? WHERE registrant0Email = ? AND registrant1Email = ?" );
392                        database.statementCache().put( sKey, s );
393                    }
394                    s.setString( 1, edge.getBar() );
395                    s.setString( 2, edge.registrant0().email() );
396                    s.setString( 3, edge.registrant1().email() );
397                    final int updatedRows = s.executeUpdate();
398                    if( updatedRows > 0 ) { assert updatedRows == 1; return; }
399                }
400                {
401                    final String sKey = statementKeyBase + "putI";
402                    PreparedStatement s = database.statementCache().get( sKey );
403                    if( s == null )
404                    {
405                        s = c.prepareStatement( "INSERT INTO " + qTable
406                          + " (registrant0Email, registrant1Email, bar) SELECT ?, ?, ?"
407                          + " WHERE NOT EXISTS (SELECT 1 FROM " + qTable
408                          +   " WHERE registrant0Email = ? AND registrant1Email = ?)" );
409                        database.statementCache().put( sKey, s );
410                    }
411                    s.setString( 1, edge.registrant0().email() );
412                    s.setString( 2, edge.registrant1().email() );
413                    s.setString( 3, edge.getBar() );
414                    s.setString( 4, edge.registrant0().email() );
415                    s.setString( 5, edge.registrant1().email() );
416                    s.executeUpdate();
417                }
418            }
419        }
420
421
422
423        /** The file-based counterpart to this table.
424          */
425        ReadyDirectory readyDirectory() { return readyDirectory; }
426
427
428            private final ReadyDirectory readyDirectory; // final after init
429
430
431
432        /** The name of the table's schema.
433          */
434        public static final String SCHEMA_NAME = "out_trace";
435
436
437
438    }
439
440
441
442//// P r i v a t e ///////////////////////////////////////////////////////////////////////
443
444
445    private boolean isChanged;
446
447
448
449}