001package votorola.a.trust; // Copyright 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 votorola.a.*; 007import votorola.a.voter.*; 008import votorola.g.*; 009import votorola.g.lang.*; 010import votorola.g.sql.*; 011 012 013/** A registrant's membership in a division. 014 * 015 * @see <a href='http://reluk.ca/w/Property:Division' 016 * >zelea.com/w/Property:Division</a> 017 */ 018public final class Membership 019{ 020 021 022 /** Creates a Membership with default values. 023 */ 024 Membership( IDPair _registrant, String _division ) 025 { 026 if( _registrant == null || _division == null ) throw new NullPointerException(); // fail fast 027 028 registrant = _registrant; 029 division = _division; 030 isChanged = true; 031 } 032 033 034 035 // ------------------------------------------------------------------------------------ 036 037 038 /** The registrant. 039 */ 040 public IDPair registrant() { return registrant; } 041 042 043 private final IDPair registrant; 044 045 046 047 /** A division in which the registrant is a member. 048 */ 049 public String division() { return division; } 050 051 052 private final String division; 053 054 055 056 /** Writes this membership to the table if it has unwritten changes. 057 */ 058 void write( final Table table ) throws SQLException 059 { 060 if( !isChanged ) return; 061 062 table.put( Membership.this ); 063 isChanged = false; 064 } 065 066 067 068 // - O b j e c t ---------------------------------------------------------------------- 069 070 071 /** Constructs a description of the membership. 072 */ 073 public @Override String toString() 074 { 075 return "Membership[" + registrant() + "," + division() + "]"; 076 } 077 078 079 080 // ==================================================================================== 081 082 083 /** The relational store of memberships that (in part) backs a compiled network trace. 084 * 085 * @see votorola.a.trust.TraceNodeW.Table 086 */ 087 public static @ThreadSafe final class Table 088 { 089 090 091 /** Constructs a Table. 092 */ 093 public Table( final ReadyDirectory readyDirectory, final Database database ) 094 throws IOException, SQLException 095 { 096 this.readyDirectory = readyDirectory; 097 this.database = database; 098 099 synchronized( database ) { database.ensureSchema( SCHEMA_NAME ); } 100 final String snapSuffix = OutputStore.suffix( 101 readyDirectory.snapDirectory().getName() ); 102 if( !OutputStore.isY4MDS( snapSuffix )) throw new VotorolaRuntimeException( "improperly suffixed snap directory parent of ready directory: " + readyDirectory ); 103 104 tableName = snapSuffix.substring(1) + OutputStore.SUFFIX_DELIMITER 105 + "membership" + OutputStore.suffix(readyDirectory.getCanonicalFile().getName()); 106 statementKeyBase = getClass().getName() + ":" + SCHEMA_NAME + "/" + tableName + "."; 107 } 108 109 110 111 private final String statementKeyBase; 112 113 114 115 private final String tableName; 116 117 118 119 // -------------------------------------------------------------------------------- 120 121 122 /** Creates this table in the database. 123 */ 124 void create() throws SQLException 125 { 126 final String sKey = statementKeyBase + "create"; 127 synchronized( database ) 128 { 129 PreparedStatement s = database.statementCache().get( sKey ); 130 if( s == null ) 131 { 132 s = database.connection().prepareStatement( 133 "CREATE TABLE \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" 134 + " (registrantEmail character varying," 135 + " division character varying," 136 + " PRIMARY KEY (registrantEmail, division))" ); 137 138 // Changing table structure? Then also increment NetworkTrace.serialVersionUID. 139 140 database.statementCache().put( sKey, s ); 141 } 142 s.execute(); 143 } 144 } 145 146 147 148 /** The database in which this table is stored. 149 */ 150 @Warning("thread restricted object") Database database() { return database; } 151 152 153 private final Database database; 154 155 156 157 /** Retrieves the set of all divisions for the specified registrant. 158 * 159 * @return a newly constructed set. 160 */ 161 public HashSet<String> divisionSet( final String registrantEmail ) throws SQLException 162 { 163 final String sKey = statementKeyBase + "divisionSet"; 164 synchronized( database ) 165 { 166 PreparedStatement s = database.statementCache().get( sKey ); 167 if( s == null ) 168 { 169 s = database.connection().prepareStatement( 170 "SELECT division" 171 + " FROM \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" 172 + " WHERE registrantEmail = ?" ); 173 database.statementCache().put( sKey, s ); 174 } 175 s.setString( 1, registrantEmail ); 176 final ResultSet r = s.executeQuery(); 177 try 178 { 179 final HashSet<String> set = new HashSet<String>(); 180 while( r.next() ) set.add( r.getString( 1 )); 181 return set; 182 } 183 finally{ r.close(); } 184 } 185 } 186 187 188 189 /** Drops this table from the database if it exists. 190 * 191 * @return true if any rows were actually removed as a result, false otherwise. 192 */ 193 final boolean drop() throws SQLException 194 { 195 final String sKey = statementKeyBase + "drop"; 196 synchronized( database ) 197 { 198 PreparedStatement s = database.statementCache().get( sKey ); 199 if( s == null ) 200 { 201 s = database.connection().prepareStatement( 202 "DROP TABLE IF EXISTS \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" ); 203 database.statementCache().put( sKey, s ); 204 } 205 final int updatedRows = s.executeUpdate(); 206 return updatedRows > 0; 207 } 208 } 209 210 211 212 /** Stores a membership in this table. 213 */ 214 void put( final Membership membership ) throws SQLException 215 { 216 synchronized( database ) 217 { 218 final String sKey = statementKeyBase + "put"; 219 PreparedStatement s = database.statementCache().get( sKey ); 220 if( s == null ) 221 { 222 s = database.connection().prepareStatement( 223 "INSERT INTO \"" + SCHEMA_NAME + "\".\"" + tableName + "\"" 224 + " (registrantEmail, division) VALUES (?, ?)" ); 225 database.statementCache().put( sKey, s ); 226 } 227 s.setString( 1, membership.registrant().email() ); 228 s.setString( 2, membership.division() ); 229 s.executeUpdate(); 230 } 231 } 232 233 234 235 /** The file-based counterpart to this table. 236 */ 237 ReadyDirectory readyDirectory() { return readyDirectory; } 238 239 240 private final ReadyDirectory readyDirectory; // final after init 241 242 243 244 /** The name of the table's schema. 245 */ 246 static final String SCHEMA_NAME = "out_trace"; 247 248 249 250 } 251 252 253 254//// P r i v a t e /////////////////////////////////////////////////////////////////////// 255 256 257 private boolean isChanged; 258 259 260 261}