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 java.util.*; import javax.script.*; import votorola.a.*; import votorola.a.voter.*; import votorola.g.*; import votorola.g.lang.*; import votorola.g.sql.*; /** A formal expression of trust that extends from one registrant (source) to another * (destination) in a trust network. * * @see ../../../s/manual.xht#Trust * @see TraceNode */ public final class TrustEdge { /** Creates a TrustEdge with default values. */ public TrustEdge( IDPair _registrant0, IDPair _registrant1 ) { if( _registrant0 == null || _registrant1 == null ) throw new NullPointerException(); // fail fast registrant0 = _registrant0; registrant1 = _registrant1; isChanged = true; } /** Constructs a TrustEdge. */ TrustEdge( IDPair _registrant0, IDPair _registrant1, String bar ) { if( _registrant0 == null || _registrant1 == null ) throw new NullPointerException(); // fail fast // Adding fields here? Increment NetworkTrace.serialVersionUID. registrant0 = _registrant0; registrant1 = _registrant1; bar = bar; } // ------------------------------------------------------------------------------------ /** Describes the bar against the trust edge, if any has been set. The description is * intended for users to read. It should include details that may be of help in * overcoming the bar. For instance, if the edge is barred because of a problem with * a particular registration property (geohandle for instance), then the bar should * point out the exact problem with that property (the specific syntax error, or * whatever). * *

A barred trust edge has no effect on the trust network. It extends no * trust.

* * @return description of bar, or null if no bar is set * * @see #setBar(String) */ public String getBar() { return bar; } private String bar; /** Sets a bar against the edge. * * @see #getBar() */ public void setBar( String newBar ) { if( ObjectX.nullEquals( newBar, bar )) return; bar = newBar; isChanged = true; } /** The trust source. */ public IDPair registrant0() { return registrant0; } private final IDPair registrant0; /** The trust destination. */ public IDPair registrant1() { return registrant1; } private final IDPair registrant1; /** Writes this edge to the table if it has unwritten changes. */ public void write( final Table table ) throws SQLException { if( !isChanged ) return; table.put( TrustEdge.this ); isChanged = false; } // - O b j e c t ---------------------------------------------------------------------- /** Constructs a description of the edge, including the names of both registrants. */ public @Override String toString() { return "TrustEdge[" + registrant0() + "," + registrant1() + "]"; } // ==================================================================================== /** The extension of {@linkplain TrustEdge trust edges} to a primary destination. The * source of the extension is always the root (registrar). Unlike an ordinary * extension, the source may extend any number of edges to the primary destination. * * @see ../../../s/manual.xht#primary-trust-edge */ public static @ThreadSafe final class Primary { /** Constructs a Primary. */ public Primary( IDPair _registrant1, int _edgeCount ) { registrant1 = _registrant1; edgeCount = _edgeCount; } /** The number of edges extended to the primary destination. */ public int edgeCount() { return edgeCount; } private final int edgeCount; /** The destination of the primary edge. */ public IDPair registrant1() { return registrant1; } private final IDPair registrant1; } // ==================================================================================== /** The relational store of trust edges that (in part) backs a compiled network trace. * * @see votorola.a.trust.TraceNodeW.Table */ public static @ThreadSafe final class Table { /** Constructs a Table. */ public Table( final ReadyDirectory readyDirectory, final Database database ) throws IOException, SQLException { this.readyDirectory = readyDirectory; this.database = database; synchronized( database ) { database.ensureSchema( SCHEMA_NAME ); } final String snapSuffix = OutputStore.suffix( readyDirectory.snapDirectory().getName() ); if( !OutputStore.isY4MDS( snapSuffix )) throw new VotorolaRuntimeException( "improperly suffixed snap directory parent of ready directory: " + readyDirectory ); tableName = snapSuffix.substring(1) + OutputStore.SUFFIX_DELIMITER + "trust_edge" + OutputStore.suffix(readyDirectory.getCanonicalFile().getName()); statementKeyBase = getClass().getName() + ":" + SCHEMA_NAME + "/" + tableName + "."; } /** For each trust edge in the table, calculate and store its edge bar, if any. * * @param nodeTable containing all registered users */ public void init_bars( final Trustserver trustserver, final TraceNodeW.Table nodeTable ) throws ScriptException, SQLException { final String sKey = statementKeyBase + "init_bars"; synchronized( database ) { PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = database.connection().prepareStatement( "SELECT registrant0Email,registrant1Email,bar" + " FROM \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" + " ORDER BY registrant0Email" ); // ordered to allow reuse of node0, below (maybe FIX to use TraceNodeW.NodeRunner) database.statementCache().put( sKey, s ); } final ResultSet r = s.executeQuery(); try { TraceNodeW node0 = null; // to reuse for efficiency, per 'ORDER BY' above while( r.next() ) { final TrustEdge edge = new TrustEdge( IDPair.fromEmail(r.getString(1)), IDPair.fromEmail(r.getString(2)), r.getString(3) ); if( node0 == null || !node0.registrant().equalsEmail( edge.registrant0() )) { node0 = nodeTable.get( edge.registrant0() ); if( node0 == null ) { assert false: "each truster is registered in nodeTable"; // maybe a stale, deleted page in streetwiki cache continue; } } final String bar; final TraceNodeW node1 = nodeTable.get( edge.registrant1() ); if( node1 == null ) { edge.setBar( (String) trustserver.runtimeConfigurationScript().invokeKnownFunction( "trustBarUnregistered", edge.registrant1() )); } else { trustserver.runtimeConfigurationScript().invokeKnownFunction( "extendingTrust", new TrustExtensionContext( trustserver, node0, node1, edge )); } edge.write( Table.this ); // if changed } } finally{ r.close(); } } } private final String statementKeyBase; private final String tableName; // -------------------------------------------------------------------------------- /** Creates this table in the database. */ public void create() throws SQLException { final String sKey = statementKeyBase + "create"; synchronized( database ) { PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = database.connection().prepareStatement( "CREATE TABLE \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" + " (registrant0Email character varying," + " registrant1Email character varying," + " bar character varying," + " PRIMARY KEY (registrant0Email, registrant1Email))" ); // Changing table structure? Then also increment NetworkTrace.serialVersionUID. database.statementCache().put( sKey, s ); } s.execute(); } } /** The database in which this table is stored. */ @Warning("thread restricted object") Database database() { return database; } private final Database database; /** Drops this table from the database if it exists. * * @return true if any rows were actually removed as a result, false otherwise. */ public final boolean drop() throws SQLException { final String sKey = statementKeyBase + "drop"; synchronized( database ) { PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = database.connection().prepareStatement( "DROP TABLE IF EXISTS \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" ); database.statementCache().put( sKey, s ); } final int updatedRows = s.executeUpdate(); return updatedRows > 0; } } /** Retrieves all edges that extend from the specified source registrant. */ List listEdgesFrom( final IDPair registrant0 ) throws SQLException { if( registrant0 == null ) throw new NullPointerException(); // fail fast final String sKey = statementKeyBase + "listEdgesFrom"; synchronized( database ) { PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = database.connection().prepareStatement( "SELECT registrant1Email,bar" + " FROM \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" + " WHERE registrant0Email = ?" ); database.statementCache().put( sKey, s ); } s.setString( 1, registrant0.email() ); final ResultSet r = s.executeQuery(); try { final ArrayList edgeList = new ArrayList(); while( r.next() ) { edgeList.add( new TrustEdge( registrant0, IDPair.fromEmail(r.getString(1)), r.getString(2) )); } return edgeList; } finally{ r.close(); } } } /** Stores an edge in this table. */ void put( final TrustEdge edge ) throws SQLException { final String qTable = "\"" + SCHEMA_NAME + "\".\"" + tableName + "\""; synchronized( database ) { // effect an "upsert" in PostgreSQL // http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql/6527838#6527838 final Connection c = database.connection(); { final String sKey = statementKeyBase + "putU"; PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = c.prepareStatement( "UPDATE " + qTable + " SET bar = ? WHERE registrant0Email = ? AND registrant1Email = ?" ); database.statementCache().put( sKey, s ); } s.setString( 1, edge.getBar() ); s.setString( 2, edge.registrant0().email() ); s.setString( 3, edge.registrant1().email() ); final int updatedRows = s.executeUpdate(); if( updatedRows > 0 ) { assert updatedRows == 1; return; } } { final String sKey = statementKeyBase + "putI"; PreparedStatement s = database.statementCache().get( sKey ); if( s == null ) { s = c.prepareStatement( "INSERT INTO " + qTable + " (registrant0Email, registrant1Email, bar) SELECT ?, ?, ?" + " WHERE NOT EXISTS (SELECT 1 FROM " + qTable + " WHERE registrant0Email = ? AND registrant1Email = ?)" ); database.statementCache().put( sKey, s ); } s.setString( 1, edge.registrant0().email() ); s.setString( 2, edge.registrant1().email() ); s.setString( 3, edge.getBar() ); s.setString( 4, edge.registrant0().email() ); s.setString( 5, edge.registrant1().email() ); s.executeUpdate(); } } } /** The file-based counterpart to this table. */ ReadyDirectory readyDirectory() { return readyDirectory; } private final ReadyDirectory readyDirectory; // final after init /** The name of the table's schema. */ public static final String SCHEMA_NAME = "out_trace"; } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private boolean isChanged; }