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}