001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.tools.versioning;
007:
008: import java.sql.Connection;
009: import java.sql.PreparedStatement;
010: import java.sql.ResultSet;
011: import java.sql.Statement;
012: import java.util.Iterator;
013: import java.util.LinkedList;
014:
015: import org.jasig.portal.RDBMServices;
016: import org.apache.commons.logging.Log;
017: import org.apache.commons.logging.LogFactory;
018:
019: /**
020: * Provides access to and persistence of version information for pieces of
021: * code installed in the portal. Identification of pieces of code is by
022: * functional name. Version is represented by three integers. In most
023: * significant order these are Major, Minor, and Micro.
024: *
025: * @author Mark Boyd {@link <a href="mailto:mark.boyd@engineer.com">mark.boyd@engineer.com</a>}
026: * @version $Revision: 36690 $
027: */
028: public class VersionsManager {
029: private static final Log log = LogFactory
030: .getLog(VersionsManager.class);
031: private static final Version[] VERSIONS_ARRAY_TYPE = new Version[] {};
032: private static final VersionsManager instance = new VersionsManager();
033: private static LinkedList versions = loadVersions();
034:
035: private VersionsManager() {
036: }
037:
038: /**
039: * Loads all version information from the back end database and returns it
040: * as a LinkedList containing Version objects.
041: *
042: * @return java.util.LinkedList
043: */
044: private static LinkedList loadVersions() {
045: LinkedList list = new LinkedList();
046: Connection con = null;
047: try {
048: con = RDBMServices.getConnection();
049: Statement sql = con.createStatement();
050: sql
051: .execute("SELECT FNAME, DESCRIPTION, MAJOR, MINOR, MICRO FROM UP_VERSIONS");
052: ResultSet rs = sql.getResultSet();
053: while (rs.next()) {
054: list.add(new Version(rs.getString(1), rs.getString(2),
055: rs.getInt(3), rs.getInt(4), rs.getInt(5)));
056: }
057: } catch (Exception e) {
058: log
059: .error(
060: "Unable to load Version information for uPortal objects.",
061: e);
062: } finally {
063: RDBMServices.releaseConnection(con);
064: }
065:
066: return list;
067: }
068:
069: /**
070: * Updates the cached version information from the database.
071: *
072: */
073: private static synchronized void updateVersions() {
074: versions = loadVersions();
075: }
076:
077: /**
078: * Returns the singleton instance of the VersionsManager.
079: *
080: * @return VersionManager
081: */
082: public static final VersionsManager getInstance() {
083: return instance;
084: }
085:
086: /**
087: * Returns an array of Versions representing all version information
088: * registered with the VersionsManager.
089: *
090: * @return Version[]
091: */
092: public Version[] getVersions() {
093: return (Version[]) versions.toArray(VERSIONS_ARRAY_TYPE);
094: }
095:
096: /**
097: * Returns the version for a specified functional name or null if no
098: * version information is available.
099: *
100: * @param fname java.lang.String
101: * @return Version
102: */
103: public Version getVersion(String fname) {
104:
105: for (Iterator i = versions.iterator(); i.hasNext();) {
106: Version v = (Version) i.next();
107: if (v.getFname().equals(fname))
108: return v;
109: }
110: return null;
111: }
112:
113: /**
114: * Removes version information for the specified functional name. Returns
115: * true if version information existed and was removed.
116: *
117: * @param fname java.lang.String
118: * @return boolean
119: *
120: */
121: public synchronized boolean removeVersion(String fname) {
122: Version v = getVersion(fname);
123:
124: if (v == null)
125: return false;
126: boolean changed = remove(v);
127:
128: updateVersions();
129: return changed;
130: }
131:
132: /**
133: * Removes this version from the backend database. Returns true if version
134: * information was indeed removed.
135: *
136: * @param v
137: * @return boolean
138: */
139: private boolean remove(Version v) {
140: Connection con = null;
141: try {
142: con = RDBMServices.getConnection();
143: PreparedStatement sql = con
144: .prepareStatement("DELETE FROM UP_VERSIONS "
145: + "WHERE FNAME=? AND MAJOR=? AND MINOR=? AND MICRO=?");
146: sql.setString(1, v.getFname());
147: sql.setInt(2, v.getMajor());
148: sql.setInt(3, v.getMinor());
149: sql.setInt(4, v.getMicro());
150: int modifiedCount = sql.executeUpdate();
151:
152: return modifiedCount >= 1;
153: } catch (Exception e) {
154: log.error("Unable to remove Version information for "
155: + v.toString(), e);
156: } finally {
157: RDBMServices.releaseConnection(con);
158: }
159: return false;
160: }
161:
162: /**
163: * Updates the version information for the indicated functional name to
164: * the passed in values only if:
165: *
166: * a) a version already exists and the values in the database match those
167: * obtained via getVersion(fname). A database update is performed in this
168: * case.
169: *
170: * b) a version does not already exist for this functional name. A database
171: * insert is performed in this case.
172: *
173: * Returns true if this call resulted in a database change. Use of primary
174: * keys in the database table is critical for (b) in the scenario where
175: * more than one portal is running against the same database to prevent two
176: * inserts to the table from succeeding and masking which portal
177: * successfully changed the version.
178: *
179: * @param fname
180: * @param major
181: * @param minor
182: * @param micro
183: * @return boolean
184: */
185: public synchronized boolean setVersion(String fname,
186: String description, int major, int minor, int micro) {
187: Version v = getVersion(fname);
188: boolean changed = false;
189:
190: if (v == null)
191: changed = insertVersion(fname, description, major, minor,
192: micro);
193: else
194: changed = updateVersion(v, new Version(fname, description,
195: major, minor, micro));
196: updateVersions();
197: return changed;
198: }
199:
200: /**
201: * Attempts to update version information in the database. Returns
202: * true if the update was successful, false otherwise. If multiple portals
203: * share the same database two portals may try this at the same time and
204: * one should fail since the major, minor, and/or micro versions had in
205: * cache will not map to those now in the database.
206: *
207: * @param old
208: * @param next
209: * @return boolean
210: */
211: private boolean updateVersion(Version old, Version next) {
212: Connection con = null;
213: try {
214: con = RDBMServices.getConnection();
215: PreparedStatement sql = con
216: .prepareStatement("UPDATE UP_VERSIONS SET MAJOR=?, MINOR=?, MICRO=?, DESCRIPTION=? "
217: + "WHERE FNAME=? AND MAJOR=? AND MINOR=? AND MICRO=?");
218: sql.setInt(1, next.getMajor());
219: sql.setInt(2, next.getMinor());
220: sql.setInt(3, next.getMicro());
221: sql.setString(4, next.getDescription());
222: sql.setString(5, old.getFname());
223: sql.setInt(6, old.getMajor());
224: sql.setInt(7, old.getMinor());
225: sql.setInt(8, old.getMicro());
226: int modifiedCount = sql.executeUpdate();
227:
228: if (modifiedCount > 1)
229: log
230: .error("Warning: Multiple version entries detected in UP_VERSION"
231: + " table. Primary keys must be used in this table when "
232: + "coordinating version information from multiple portals "
233: + "running against the same database.");
234: return modifiedCount >= 1;
235: } catch (Exception e) {
236: log
237: .error(
238: "Unable to update version information for "
239: + old.toString()
240: + ". Update from external source assumed. Abandoning update.",
241: e);
242: } finally {
243: RDBMServices.releaseConnection(con);
244: }
245: return false;
246: }
247:
248: /**
249: * Attempts to insert new version information into the database. Returns
250: * true if the insert was successful, false otherwise. If multiple portals
251: * share the same database two portals may try this at the same time and
252: * one should fail provided primary keys have been specified for the table.
253: *
254: * @param fname
255: * @param major
256: * @param minor
257: * @param micro
258: * @return boolean
259: */
260: private boolean insertVersion(String fname, String description,
261: int major, int minor, int micro) {
262: Connection con = null;
263: Version v = new Version(fname, description, major, minor, micro);
264:
265: try {
266: con = RDBMServices.getConnection();
267: PreparedStatement sql = con
268: .prepareStatement("INSERT INTO UP_VERSIONS (FNAME, DESCRIPTION, MAJOR, MINOR, MICRO) "
269: + "VALUES (?,?,?,?,?)");
270: sql.setString(1, fname);
271: sql.setString(2, description);
272: sql.setInt(3, major);
273: sql.setInt(4, minor);
274: sql.setInt(5, micro);
275: int modifiedCount = sql.executeUpdate();
276:
277: return modifiedCount == 1;
278: } catch (Exception e) {
279: log.error("Unable to insert version information for "
280: + v.toString() + ". Abandoning insert.", e);
281: } finally {
282: RDBMServices.releaseConnection(con);
283: }
284: return false;
285: }
286: }
|