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}