001/*
002 * Copyright (c) 2003-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2016-07-19 06:26:44 +0000 (Tue, 19 Jul 2016) $' 
007 * '$Revision: 34509 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.objectmanager.lsid;
031
032import java.sql.PreparedStatement;
033import java.sql.ResultSet;
034import java.sql.SQLException;
035import java.sql.Statement;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.kepler.configuration.ConfigurationManager;
040import org.kepler.configuration.ConfigurationProperty;
041import org.kepler.util.AuthNamespace;
042import org.kepler.util.sql.DatabaseFactory;
043import org.kepler.util.sql.DatabaseType;
044
045import ptolemy.util.MessageHandler;
046
047/**
048 * This class maintains a list of LSIDs in the system and can generate new LSIDs
049 * that are unique to the local system. 
050 *
051 *@created June 20, 2005
052 */
053
054public class LSIDGenerator {
055    
056        private static final Log log = LogFactory.getLog(LSIDGenerator.class
057                        .getName());
058        private static final boolean isDebugging = log.isDebugEnabled();
059
060        // singleton instance
061        private static LSIDGenerator generator = null;
062
063        /** The AuthNamespace object. */
064        private AuthNamespace _authNamespace;
065
066        /** The name of the lsid generator table. */
067        private static final String LSID_GENERATOR_TABLE_NAME = "LSID_GENERATOR";
068
069        /** SQL to create the lsid generator table.
070         *  
071         *  FIXME convert to org.kepler.util.sql.Schema to support non-HSQL dbs
072         *  
073         *  The LSID_GENERATOR table is used to record the highest Revision for an
074         *  object ID that has been generated by this Kepler instance for a specific
075         *  authority and namespace.
076     *      AUTH - the Authority
077     *      NS - the NameSpace
078     *      OID - the highest Object ID assigned by this Kepler instance
079     *      REV - the highest Revision assigned by this Kepler instance
080     */
081         private static final String LSID_GENERATOR_TABLE_CREATE_SQL =
082             "create cached table LSID_GENERATOR (AUTH varchar, NS varchar, " +
083             "OID bigint, REV bigint, PRIMARY KEY (AUTH,NS,OID) )";
084
085        /** The database. */
086        private static DatabaseType _dbType;
087        
088        /**
089         * Convenience reference. Make sure to close your ResultSets since we're
090         * reusing the Statement.
091         */
092        private static Statement _stmt;
093        
094        private static PreparedStatement _insertPrepStmt;
095        private static PreparedStatement _updatePrepStmt;
096        
097        /** The name of the database. */
098        public static final String CORE_DB_STR = "coreDB";
099        
100        /** Builds a new instance of the LSIDGenerator. NOTE: the
101         *  constructor is private so that only one instance may be created.
102         */
103    private LSIDGenerator() throws Exception {
104        
105        openDatabase();
106        
107        // Every time we get a new connection to the LSID_GENERATOR
108        // table, check it to make sure the current AuthorizedNamespace
109        // has at least one row in it, if it does not, get a new
110        // AuthorizedNamespace (this means the LSID_GENERATOR table was
111        // deleted somehow
112        int rows = countRowsForCurrentAuthNamespace();
113        if (rows <= 0) {
114            AuthNamespace an = AuthNamespace.getInstance();
115            an.getNewAuthorizedNamespace();
116        }
117
118        _authNamespace = AuthNamespace.getInstance();
119
120        }
121        
122        /**
123         * returns a singleton instance of this class
124         */
125        public static synchronized LSIDGenerator getInstance() {
126                if (generator == null) {
127                        try {
128                                generator = new LSIDGenerator();
129                        } catch (Exception e) {
130                            MessageHandler.error("Error creating LSID generator.", e);
131                        }
132                }
133                return generator;
134        }
135
136        /**
137         * Return a new LSID using the Authority and Namespace for this Kepler
138         * Instance and the NextObjectID for this instance with a revision number of
139         * 1.
140         * 
141         * @return a new KeplerLSID that is globally unique
142         * @throws Exception
143         */
144        public synchronized KeplerLSID getNewLSID() throws Exception {
145                if (isDebugging)
146                        log.debug("getNewLSID()");
147
148                KeplerLSID lsid = null;
149
150                Long nextObjectID = getNextObjectID();
151
152                lsid = new KeplerLSID(_authNamespace.getAuthority(), _authNamespace
153                .getNamespace(), nextObjectID, Long.valueOf(1));
154                
155                boolean success = recordLsidGeneration(lsid);
156                if (success) {
157                        if (isDebugging)
158                                log.debug("newLSID: " + lsid);
159                        return lsid;
160                }
161                return null;
162        }
163        
164        /**
165         * Update the revision of a KeplerLSID.
166         * 
167         * @param lsid
168         * @return
169         */
170        public synchronized KeplerLSID updateLsidRevision(KeplerLSID lsid) {
171                if (isDebugging)
172                        log.debug("updateLsidRevision("+lsid.toString()+")");
173                
174                if ( !lsid.getAuthority().equals(_authNamespace.getAuthority()) 
175                                || !lsid.getNamespace().equals(_authNamespace.getNamespace())) {
176                        return null;
177                }
178                
179                try {
180                        Long lastRevision = null;
181
182                        // FIXME make a prepared statement
183                        String query = "select REV from " + LSID_GENERATOR_TABLE_NAME
184                                + " where auth = '" + lsid.getAuthority() + "' "
185                                + " and ns = '" + lsid.getNamespace() + "' "
186                                + " and oid = " + lsid.getObject();
187                        if (isDebugging) log.debug(query);
188                        ResultSet rs = null;
189            try {
190                rs = _stmt.executeQuery(query);
191                if (rs == null)
192                    throw new SQLException("Query Failed: " + query);
193                if (rs.next()) {
194                    lastRevision = rs.getLong(1);
195                    if (rs.wasNull())
196                        throw new SQLException("Last Revision was Null");
197                    if (rs.next())
198                        throw new SQLException(LSID_GENERATOR_TABLE_NAME
199                                + " table is corrupt");
200                }
201            } finally {
202                if (rs != null) {
203                    rs.close();
204                }
205            }
206                        
207            // FIXME lastRevision could be null
208                        lsid.setRevision(lastRevision + 1L);
209
210                        _updatePrepStmt.setLong(1, lsid.getRevision());
211                        _updatePrepStmt.setString(2, lsid.getAuthority());
212                        _updatePrepStmt.setString(3, lsid.getNamespace());
213                        _updatePrepStmt.setLong(4, lsid.getObject());
214                        _updatePrepStmt.executeUpdate();
215                        _updatePrepStmt.clearParameters();
216                        
217                        //_conn.commit();
218                        
219                } catch (SQLException e) {
220                        e.printStackTrace();
221                }
222                
223                return lsid;
224                
225        }
226        
227        /**
228         * Record the generation of a new LSID in the LSID_GENERATOR table.
229         * 
230         * @param lsid
231         * @return
232         */
233        private boolean recordLsidGeneration(KeplerLSID lsid) {
234        if (isDebugging)
235            log.debug("recordLsidGeneration(" + lsid.toString() + ")");
236        boolean success = false;
237
238        try {
239            insertRow(lsid.getAuthority(), lsid.getNamespace(), lsid
240                    .getObject(), lsid.getRevision());
241            success = true;
242        } catch (Exception e) {
243            e.printStackTrace();
244        }
245
246        return success;
247    }
248        
249        /** Insert a row into the LSID generator table. */
250        public static synchronized void insertRow(String authority, String namespace,
251            long object, long revision) throws Exception {
252            
253            // since this method is static, make sure the database has
254            // been opened and prepared statements created.
255            if(_dbType == null) {
256                openDatabase();
257            }
258            
259        try {
260            _insertPrepStmt.clearParameters();
261            _insertPrepStmt.setString(1, authority);
262            _insertPrepStmt.setString(2, namespace);
263            _insertPrepStmt.setLong(3, object);
264            _insertPrepStmt.setLong(4, revision);
265            _insertPrepStmt.executeUpdate();
266        } catch (SQLException e) {
267            throw new Exception("Error inserting into lsid generator table: "
268                    + e.getMessage());
269        }
270    }
271        
272        /** Open the database containing the lsid generator table and intialize
273         *  prepared statements.
274         */
275        private static void openDatabase() throws Exception
276        {
277            if(_dbType == null)
278            {
279                ConfigurationManager configManager = ConfigurationManager.getInstance();
280                ConfigurationProperty coreProperty = configManager
281                        .getProperty(ConfigurationManager.getModule("core"));
282                ConfigurationProperty coreDBProperty = coreProperty
283                        .getProperty(CORE_DB_STR);
284
285                if (coreDBProperty == null) {
286                    throw new Exception("Could not find " + CORE_DB_STR
287                            + " in core module's configuration.xml.");
288                }
289
290                _dbType = DatabaseFactory.getConnectedDatabaseType(coreDBProperty);
291
292                // By creating the statement and keeping it around
293                // make sure to close your resultsets to save memory
294                _stmt = _dbType.getStatement();
295
296                // create lsid generator table if it does not exist.
297                if(!_dbType.tableExists(LSID_GENERATOR_TABLE_NAME))
298                {
299                    _stmt.execute(LSID_GENERATOR_TABLE_CREATE_SQL);
300                }
301
302                _insertPrepStmt = _dbType.getPrepStatement("insert into "
303                        + LSID_GENERATOR_TABLE_NAME
304                        + " (AUTH,NS,OID,REV) values (?,?,?,?)");
305                _updatePrepStmt = _dbType.getPrepStatement("update "
306                        + LSID_GENERATOR_TABLE_NAME + " SET REV=? "
307                        + " WHERE AUTH=? and NS=? and OID=?");
308            }
309        }
310
311        /**
312         * Return the next available Object ID for the current authority and namespace.
313         * 
314         * @throws Exception
315         */
316        private Long getNextObjectID() throws Exception {
317                if (isDebugging)
318                        log.debug("getNextObjectID()");
319
320                Long nextOID = null;
321                
322        // FIXME make a prepared statement
323                String query = "select max(OID) from " + LSID_GENERATOR_TABLE_NAME
324                        + " where auth = '" + _authNamespace.getAuthority() + "' "
325                        + " and ns = '" + _authNamespace.getNamespace() + "'";
326                if (isDebugging) log.debug(query);
327                ResultSet rs = null;
328                try {
329            rs = _stmt.executeQuery(query);
330            if (rs == null)
331                throw new SQLException("Query Failed: " + query);
332            if (rs.next()) {
333                Long maxOID = rs.getLong(1);
334                nextOID = maxOID + 1L;
335            } else {
336                nextOID = 1L;
337            }
338        } finally {
339            if (rs != null) {
340                rs.close();
341            }
342        }
343                
344                if (nextOID != null && nextOID > 0) {
345                        if (isDebugging) {
346                                log.debug("LastObjectID: " + nextOID);
347                        }
348                        return nextOID;
349                }
350                return null;
351        }
352        
353        /**
354         * Return the number of rows in the LSID_GENERATOR table.
355         * 
356         * @return int number of rows in the LSID_GENERATOR table
357         */
358        public synchronized int countRowsForCurrentAuthNamespace() {
359                int cnt = -1;
360                try {
361                        AuthNamespace an = AuthNamespace.getInstance();
362                
363                // FIXME make a prepared statement
364                        String query = "select count(*) from " + LSID_GENERATOR_TABLE_NAME;
365                        query += " where AUTH = '" + an.getAuthority() 
366                                + "' and NS = '" + an.getNamespace() + "'";
367                        if (isDebugging) log.debug(query);
368                        ResultSet rs = null;
369                        try {
370                rs = _stmt.executeQuery(query);
371                if (rs == null)
372                    throw new SQLException("Query Failed: " + query);
373                if (rs.next()) {
374                    cnt = rs.getInt(1);
375                    if (rs.wasNull()) {
376                        cnt = -1;
377                    }
378                    if (rs.next()) {
379                        cnt = -1;
380                        throw new SQLException("Should never happen");
381                    }
382                }
383            } finally {
384                if (rs != null) {
385                    rs.close();
386                }
387            }
388                } catch (SQLException sqle) {
389                        System.out.println(sqle.getMessage());
390                        sqle.printStackTrace();
391                }
392                return cnt;
393        }
394        
395        /** Close the LSID database if opened. */
396        public synchronized void closeDatabase() {
397            if(_dbType != null) {
398                
399                try {
400                _stmt.close();
401            } catch (SQLException e) {
402                MessageHandler.error("Error closing database statement.", e);
403            }
404                
405                try {
406                _dbType.disconnect();
407            } catch (SQLException e) {
408                MessageHandler.error("Error closing LSID database.", e);
409            }
410                _dbType = null;
411                generator = null;
412            }
413        }
414}