001: /*
002: Copyright (C) 2003 Know Gate S.L. All rights reserved.
003: C/Oņa, 107 1š2 28050 Madrid (Spain)
004:
005: Redistribution and use in source and binary forms, with or without
006: modification, are permitted provided that the following conditions
007: are met:
008:
009: 1. Redistributions of source code must retain the above copyright
010: notice, this list of conditions and the following disclaimer.
011:
012: 2. The end-user documentation included with the redistribution,
013: if any, must include the following acknowledgment:
014: "This product includes software parts from hipergate
015: (http://www.hipergate.org/)."
016: Alternately, this acknowledgment may appear in the software itself,
017: if and wherever such third-party acknowledgments normally appear.
018:
019: 3. The name hipergate must not be used to endorse or promote products
020: derived from this software without prior written permission.
021: Products derived from this software may not be called hipergate,
022: nor may hipergate appear in their name, without prior written
023: permission.
024:
025: This library is distributed in the hope that it will be useful,
026: but WITHOUT ANY WARRANTY; without even the implied warranty of
027: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
028:
029: You should have received a copy of hipergate License with this code;
030: if not, visit http://www.hipergate.org or mail to info@hipergate.org
031: */
032:
033: package com.knowgate.projtrack;
034:
035: import com.knowgate.debug.DebugFile;
036:
037: import com.knowgate.dataobjs.DB;
038: import com.knowgate.jdc.JDCConnection;
039: import com.knowgate.dataobjs.DBBind;
040: import com.knowgate.dataobjs.DBPersist;
041: import com.knowgate.dataobjs.DBSubset;
042: import com.knowgate.lucene.Indexer;
043: import com.knowgate.lucene.BugIndexer;
044:
045: import com.knowgate.misc.Gadgets;
046:
047: import java.util.HashMap;
048: import java.util.Iterator;
049: import java.util.Properties;
050:
051: import java.io.FileNotFoundException;
052: import java.io.IOException;
053:
054: import java.sql.Connection;
055: import java.sql.SQLException;
056: import java.sql.CallableStatement;
057: import java.sql.PreparedStatement;
058: import java.sql.Statement;
059: import java.sql.ResultSet;
060: import java.sql.Timestamp;
061: import java.sql.Types;
062:
063: /**
064: * <p>Bug or Project Incident</p>
065: * @author Sergio Montoro Ten
066: * @version 3.0
067: */
068: public class Bug extends DBPersist {
069:
070: /**
071: * Create empty bug
072: */
073: public Bug() {
074: super (DB.k_bugs, "Bug");
075: }
076:
077: // ----------------------------------------------------------
078:
079: /**
080: * Load Bug from database.
081: * @param oConn Database Connection
082: * @param sIdBug GUID of Bug to be loaded.
083: * @throws SQLException
084: */
085: public Bug(JDCConnection oConn, String sIdBug) throws SQLException {
086: super (DB.k_bugs, "Bug");
087:
088: Object aBug[] = { sIdBug };
089:
090: load(oConn, aBug);
091: }
092:
093: // ----------------------------------------------------------
094:
095: /**
096: * Load Bug from database.
097: * @param oConn Database Connection
098: * @param iPgBug int Numeric identifier of bug to be loaded
099: * @param sWorkArea String GUID of WorkArea to which Bug belongs
100: * @throws SQLException
101: * @since 3.0
102: */
103: public Bug(JDCConnection oConn, int iPgBug, String sWorkArea)
104: throws SQLException {
105: super (DB.k_bugs, "Bug");
106: Object aBug[] = { Bug.getIdFromPg(oConn, iPgBug, sWorkArea) };
107: if (null != aBug[0])
108: load(oConn, aBug);
109: }
110:
111: // ----------------------------------------------------------
112:
113: /**
114: * <p>Delete Bug</p>
115: * Calls k_sp_del_bug stored procedure.
116: * @param oConn Database Connection
117: * @return boolean
118: * @throws SQLException
119: */
120: public boolean delete(JDCConnection oConn) throws SQLException {
121: return Bug.delete(oConn, getString(DB.gu_bug));
122: }
123:
124: // ----------------------------------------------------------
125:
126: /**
127: * <p>Delete Bug from database and from lucene index</p>
128: * @param oConn Database Connection
129: * @param oCnf Properties containing luceneindex path
130: * @return boolean
131: * @throws SQLException
132: * @throws IOException
133: * @throws NoSuchFieldException
134: * @throws IllegalAccessException
135: * @since 3.0
136: */
137: public boolean delete(JDCConnection oConn, Properties oCnf)
138: throws SQLException, IOException, NoSuchFieldException,
139: IllegalAccessException {
140: return Bug.delete(oConn, getString(DB.gu_bug), oCnf);
141: }
142:
143: // ----------------------------------------------------------
144:
145: /**
146: * <p>Store Bug and write its change log</p>
147: * This method automatically assigns a new bug number (pg_bug) if one is not
148: * supplied by calling seq_k_bugs sequence
149: * It also updates last modified date (dt_modified) and sinve v2.2 writes changes
150: * to k_bugs_changelog if that table exists
151: * @param oConn JDCConnection
152: * @return boolean
153: * @throws SQLException
154: */
155: public boolean store(JDCConnection oConn) throws SQLException {
156: int iPgBug;
157: Timestamp dtNow;
158: Object oOldValue;
159: String sSQL;
160:
161: if (DebugFile.trace) {
162: DebugFile.writeln("Begin Bug.store()");
163: DebugFile.incIdent();
164: }
165:
166: dtNow = new Timestamp(DBBind.getTime());
167:
168: if (!AllVals.containsKey(DB.gu_bug)) {
169: put(DB.gu_bug, Gadgets.generateUUID());
170: if (!AllVals.containsKey(DB.pg_bug)) {
171: iPgBug = Bug.getPgFromId(oConn, getString(DB.gu_bug));
172: if (-1 == iPgBug)
173: iPgBug = DBBind.nextVal(oConn, "seq_" + DB.k_bugs);
174: put(DB.pg_bug, iPgBug);
175: }
176: } else {
177: if (!AllVals.containsKey(DB.pg_bug)) {
178: iPgBug = Bug.getPgFromId(oConn, getString(DB.gu_bug));
179: if (-1 == iPgBug)
180: iPgBug = DBBind.nextVal(oConn, "seq_" + DB.k_bugs);
181: put(DB.pg_bug, iPgBug);
182: }
183: replace(DB.dt_modified, dtNow);
184: Bug oOld = new Bug();
185: if (oOld.load(oConn, new Object[] { get(DB.gu_bug) })) {
186: if (DBBind.exists(oConn, DB.k_bugs_changelog, "U")) {
187: HashMap oLog = changelog(oOld);
188: sSQL = "INSERT INTO " + DB.k_bugs_changelog + " ("
189: + DB.gu_bug + "," + DB.pg_bug + ","
190: + DB.nm_column + "," + DB.gu_writer + ","
191: + DB.tx_oldvalue + ") VALUES (?,?,?,?,?)";
192: if (DebugFile.trace)
193: DebugFile
194: .writeln("PreparedStatement.prepareStatement("
195: + sSQL + ")");
196: PreparedStatement oWriteLog = oConn
197: .prepareStatement(sSQL);
198: oWriteLog.setString(1, getString(DB.gu_bug));
199: oWriteLog.setInt(2, getInt(DB.pg_bug));
200: Iterator oIter = oLog.keySet().iterator();
201: while (oIter.hasNext()) {
202: String sColumnName = (String) oIter.next();
203: if (!sColumnName
204: .equalsIgnoreCase(DB.dt_modified)) {
205: oWriteLog.setString(3, sColumnName);
206: oWriteLog.setString(4, getStringNull(
207: DB.gu_writer, null));
208: oOldValue = oLog.get(sColumnName);
209: if (null == oOldValue)
210: oWriteLog.setNull(5, Types.VARCHAR);
211: else
212: oWriteLog.setString(5, Gadgets.left(
213: oOldValue.toString(), 255));
214: if (DebugFile.trace)
215: DebugFile
216: .writeln("PreparedStatement.executeUpdate("
217: + sColumnName + ")");
218: oWriteLog.executeUpdate();
219: }
220: } // wend
221: if (DebugFile.trace)
222: DebugFile.writeln("PreparedStatement.close()");
223: oWriteLog.close();
224: } // fi (exists(k_bugs_changelog))
225: } // fi (load(gu_bug))
226: } // fi (AllVals.containsKey(gu_bug))
227:
228: boolean bRetVal = super .store(oConn);
229:
230: if (DebugFile.trace) {
231: DebugFile.decIdent();
232: DebugFile.writeln("End Bug.store()");
233: }
234: return bRetVal;
235: } // store
236:
237: // ---------------------------------------------------------------------------
238:
239: /**
240: * Store bug and add it to a Lucene index
241: * @param oConn JDCConnection
242: * @param oCnf Properties containing luceneindex path
243: * @return boolean
244: * @throws SQLException
245: * @throws IOException
246: * @throws ClassNotFoundException
247: * @throws NoSuchFieldException
248: * @throws IllegalAccessException
249: * @throws InstantiationException
250: * @since 3.0
251: */
252: public boolean storeAndIndex(JDCConnection oConn, Properties oCnf)
253: throws SQLException, IOException, ClassNotFoundException,
254: NoSuchFieldException, IllegalAccessException,
255: InstantiationException {
256: if (DebugFile.trace) {
257: DebugFile
258: .writeln("Begin Bug.storeAndIndex([JDCConnection], [Properties])");
259: DebugFile.incIdent();
260: }
261: boolean bStore = store(oConn);
262: if (bStore) {
263: String sLuceneIndex = oCnf.getProperty("luceneindex", "");
264: if (sLuceneIndex.length() > 0) {
265: String sWrkA = getWorkArea(oConn, getString(DB.gu_bug));
266: BugIndexer.addBug(oCnf, oConn, sWrkA, this );
267: }
268: }
269: if (DebugFile.trace) {
270: DebugFile.decIdent();
271: DebugFile.writeln("End Bug.storeAndIndex()");
272: }
273: return bStore;
274: } // storeAndIndex
275:
276: /**
277: * Re-index bug
278: * @param oConn JDCConnection
279: * @param oCnf Properties
280: * @throws SQLException
281: * @throws IOException
282: * @throws ClassNotFoundException
283: * @throws NoSuchFieldException
284: * @throws IllegalAccessException
285: * @throws InstantiationException
286: * @since 3.0
287: */
288: public void reIndex(JDCConnection oConn, Properties oCnf)
289: throws SQLException, IOException, ClassNotFoundException,
290: NoSuchFieldException, IllegalAccessException,
291: InstantiationException {
292: if (DebugFile.trace) {
293: DebugFile
294: .writeln("Begin Bug.reIndex([JDCConnection], [Properties])");
295: DebugFile.incIdent();
296: }
297:
298: String sLuceneIndex = oCnf.getProperty("luceneindex", "");
299: if (sLuceneIndex.length() > 0) {
300: String sWrkA = getWorkArea(oConn, getString(DB.gu_bug));
301: if (null != sWrkA) {
302: Indexer.delete("k_bugs", sWrkA, oCnf,
303: getString(DB.gu_bug));
304: BugIndexer.addBug(oCnf, oConn, sWrkA, this );
305: } // fi
306: } // fi
307:
308: if (DebugFile.trace) {
309: DebugFile.decIdent();
310: DebugFile.writeln("End Bug.reIndex()");
311: }
312: } // reIndex
313:
314: // ---------------------------------------------------------------------------
315:
316: /**
317: * Insert attachment into k_bugs_attach table
318: * @param oConn JDCConnection
319: * @param sFilePath String Full path to local file
320: * @throws SQLException
321: * @throws FileNotFoundException
322: * @throws IOException
323: * @throws NullPointerException
324: * @since 3.0
325: */
326: public void attachFile(JDCConnection oConn, String sFilePath)
327: throws SQLException, FileNotFoundException, IOException,
328: NullPointerException {
329:
330: if (DebugFile.trace) {
331: DebugFile.writeln("Begin Bug.attachFile([JDCConnection],"
332: + sFilePath + ")");
333: DebugFile.incIdent();
334: }
335:
336: BugAttachment.createFromFile(oConn, getString(DB.gu_bug),
337: sFilePath);
338:
339: if (DebugFile.trace) {
340: DebugFile.decIdent();
341: DebugFile.writeln("End Bug.attachFile()");
342: }
343: } // attachFile
344:
345: // ---------------------------------------------------------------------------
346:
347: /**
348: * Remove attachment from k_bugs_attach table
349: * @param oConn JDCConnection
350: * @param sFileName String
351: * @throws SQLException
352: */
353: public void removeAttachment(JDCConnection oConn, String sFileName)
354: throws SQLException {
355: BugAttachment.delete(oConn, getString(DB.gu_bug), sFileName);
356: } // removeAttachment
357:
358: // ---------------------------------------------------------------------------
359:
360: /**
361: * Get array of attachments
362: * @param oConn JDCConnection
363: * @return BugAttachment[] Array of BugAttachment objects or <b>null</b> if this Bug has no attachments
364: * @throws SQLException
365: * @since 3.0
366: */
367: public BugAttachment[] attachments(JDCConnection oConn)
368: throws SQLException {
369: DBSubset oAttachs = new DBSubset(DB.k_bugs_attach, DB.tx_file
370: + "," + DB.len_file, DB.gu_bug + "=?", 10);
371: int iAttachs = oAttachs.load(oConn,
372: new Object[] { getString(DB.gu_bug) });
373: if (0 == iAttachs)
374: return null;
375: else {
376: BugAttachment[] aAttachs = new BugAttachment[iAttachs];
377: for (int a = 0; a < iAttachs; a++)
378: aAttachs[a] = new BugAttachment(getString(DB.gu_bug),
379: oAttachs.getString(1, a), oAttachs.getInt(2, a));
380: return aAttachs;
381: }
382: } // attachments
383:
384: // ---------------------------------------------------------------------------
385:
386: /**
387: * Get change log for all the values of a bug
388: * @param oConn JDCConnection
389: * @return BugChangeLog[]
390: * @throws SQLException
391: * @since 3.0
392: */
393: public BugChangeLog[] changeLog(JDCConnection oConn)
394: throws SQLException {
395: BugChangeLog[] aBcl;
396: DBSubset oLog = new DBSubset(DB.k_bugs_changelog, DB.gu_bug
397: + "," + DB.pg_bug + "," + DB.nm_column + ","
398: + DB.dt_modified + "," + DB.gu_writer + ","
399: + DB.tx_oldvalue, DB.gu_bug + "=? ORDER BY 4", 10);
400: int iLog = oLog.load(oConn,
401: new Object[] { getString(DB.gu_bug) });
402: if (0 == iLog) {
403: aBcl = null;
404: } else {
405: aBcl = new BugChangeLog[iLog];
406: for (int l = 0; l < iLog; l++) {
407: aBcl[l] = new BugChangeLog();
408: aBcl[l].putAll(oLog.getRowAsMap(l));
409: aBcl[l]
410: .setWriter(oConn, oLog
411: .getStringNull(4, l, null));
412: } // next
413: } // fi
414: return aBcl;
415: } // changeLog
416:
417: // ---------------------------------------------------------------------------
418:
419: /**
420: * Get change log for a column of a bug
421: * @param oConn JDCConnection
422: * @param sColumnName String
423: * @return BugChangeLog[]
424: * @throws SQLException
425: * @since 3.0
426: */
427: public BugChangeLog[] changeLog(JDCConnection oConn,
428: String sColumnName) throws SQLException {
429: BugChangeLog[] aBcl;
430: DBSubset oLog = new DBSubset(DB.k_bugs_changelog, DB.gu_bug
431: + "," + DB.pg_bug + "," + DB.nm_column + ","
432: + DB.dt_modified + "," + DB.gu_writer + ","
433: + DB.tx_oldvalue, DB.gu_bug + "=? AND " + DB.nm_column
434: + "=? ORDER BY 4", 10);
435: int iLog = oLog.load(oConn, new Object[] {
436: getString(DB.gu_bug), sColumnName });
437: if (0 == iLog) {
438: aBcl = null;
439: } else {
440: aBcl = new BugChangeLog[iLog];
441: for (int l = 0; l < iLog; l++) {
442: aBcl[l] = new BugChangeLog();
443: aBcl[l].putAll(oLog.getRowAsMap(l));
444: aBcl[l]
445: .setWriter(oConn, oLog
446: .getStringNull(4, l, null));
447: } // next
448: } // fi
449: return aBcl;
450: } // changeLog
451:
452: // ***************************************************************************
453: // Static Methods
454:
455: /**
456: * Get WorkArea to which bug belongs
457: * @param oConn JDCConnection
458: * @param sGuid String Bug GUID
459: * @return String WorkArea GUID
460: */
461: private static String getWorkArea(JDCConnection oConn, String sGuid)
462: throws SQLException {
463: String sWrkA;
464: if (DebugFile.trace) {
465: DebugFile
466: .writeln("JDCConnection.prepareStatement(SELECT p."
467: + DB.gu_owner + " FROM " + DB.k_projects
468: + " p," + DB.k_bugs + " b WHERE p."
469: + DB.gu_project + "=b." + DB.gu_project
470: + " AND b." + DB.gu_bug + "='" + sGuid
471: + "'");
472: }
473: PreparedStatement oStmt = oConn
474: .prepareStatement("SELECT p." + DB.gu_owner + " FROM "
475: + DB.k_projects + " p," + DB.k_bugs
476: + " b WHERE p." + DB.gu_project + "=b."
477: + DB.gu_project + " AND b." + DB.gu_bug + "=?",
478: ResultSet.TYPE_FORWARD_ONLY,
479: ResultSet.CONCUR_READ_ONLY);
480: oStmt.setString(1, sGuid);
481: ResultSet oRSet = oStmt.executeQuery();
482: if (oRSet.next())
483: sWrkA = oRSet.getString(1);
484: else
485: sWrkA = null;
486: oRSet.close();
487: oStmt.close();
488: return sWrkA;
489: } // getWorkArea
490:
491: /**
492: * <p>Delete Bug.</p>
493: * Typically, bugs are never deleted, but their status is changed to some
494: * definitive solved or archived condition.<br>
495: * Calls k_sp_del_bug stored procedure.
496: * @param oConn Database Connection
497: * @param sBugGUID GUID of Bug to be deleted.
498: * @throws SQLException
499: */
500: public static boolean delete(JDCConnection oConn, String sBugGUID)
501: throws SQLException {
502: boolean bRetVal;
503:
504: if (oConn.getDataBaseProduct() == JDCConnection.DBMS_POSTGRESQL) {
505: if (DebugFile.trace)
506: DebugFile
507: .writeln("Connection.executeQuery(SELECT k_sp_del_bug ('"
508: + sBugGUID + "'))");
509: Statement oStmt = oConn.createStatement();
510: ResultSet oRSet = oStmt
511: .executeQuery("SELECT k_sp_del_bug ('" + sBugGUID
512: + "')");
513: oRSet.close();
514: oStmt.close();
515: bRetVal = true;
516: } else {
517: if (DebugFile.trace)
518: DebugFile
519: .writeln("Connection.prepareCall({ call k_sp_del_bug ('"
520: + sBugGUID + "')})");
521: CallableStatement oCall = oConn
522: .prepareCall("{call k_sp_del_bug ('" + sBugGUID
523: + "')}");
524: bRetVal = oCall.execute();
525: oCall.close();
526: }
527:
528: return bRetVal;
529: } // delete()
530:
531: /**
532: * Delete bug from database and from Lucene index
533: * @param oConn JDCConnection
534: * @param sBugGUID String Bug GUID
535: * @param oCnf Properties containing luceneindex path
536: * @return boolean
537: * @throws SQLException
538: * @throws IOException
539: * @throws NoSuchFieldException
540: * @throws IllegalAccessException
541: * @since 3.0
542: */
543: public static boolean delete(JDCConnection oConn, String sBugGUID,
544: Properties oCnf) throws SQLException, IOException,
545: NoSuchFieldException, IllegalAccessException {
546: String sLuceneIndex = oCnf.getProperty("luceneindex", "");
547: if (sLuceneIndex.length() > 0) {
548: String sWrkA = getWorkArea(oConn, sBugGUID);
549: if (null != sWrkA)
550: Indexer.delete("k_bugs", sWrkA, oCnf, sBugGUID);
551: }
552: return delete(oConn, sBugGUID);
553: } // delete
554:
555: // ----------------------------------------------------------
556:
557: /**
558: * <p>Get Bug Numeric Identifier from Global Unique Identifier.</p>
559: * Each Bug is assigned a GUID. But, as GUID are 32 characters hexadecimals
560: * string very difficult to remember, each bug is also automatically assigned
561: * to an integer identifier. The bug numeric identifier is an alternative
562: * primary key.
563: * @param oConn Database Connection
564: * @param sBugId Bug GUID
565: * @return Bug Integer Identifier
566: * @throws SQLException
567: */
568: public static int getPgFromId(JDCConnection oConn, String sBugId)
569: throws SQLException {
570: int iRetVal;
571: PreparedStatement oStmt;
572: ResultSet oRSet;
573:
574: oStmt = oConn
575: .prepareStatement("SELECT " + DB.pg_bug + " FROM "
576: + DB.k_bugs + " WHERE " + DB.gu_bug + "=?",
577: ResultSet.TYPE_FORWARD_ONLY,
578: ResultSet.CONCUR_READ_ONLY);
579: oStmt.setString(1, sBugId);
580: oRSet = oStmt.executeQuery();
581: if (oRSet.next())
582: iRetVal = oRSet.getInt(1);
583: else
584: iRetVal = -1;
585: oRSet.close();
586: oStmt.close();
587: return iRetVal;
588: } // getPgFromId
589:
590: // ----------------------------------------------------------
591:
592: /**
593: * <p>Get Bug Unique Identifier from its numeric identifier.</p>
594: * Each Bug is assigned a GUID. But, as GUID are 32 characters hexadecimals
595: * string very difficult to remember, each bug is also automatically assigned
596: * to an integer identifier. The bug numeric identifier is an alternative
597: * primary key.
598: * @param oConn Database Connection
599: * @param iBugPg Bug numeric identifier
600: * @param sWorkArea GUID
601: * @return Bug GUID or <b>null</b> if no bug with such numeric identifier is found at given WorkArea
602: * @throws SQLException
603: * @since 2.2
604: */
605: public static String getIdFromPg(JDCConnection oConn, int iBugPg,
606: String sWorkArea) throws SQLException {
607: String sRetVal;
608: PreparedStatement oStmt;
609: ResultSet oRSet;
610:
611: oStmt = oConn.prepareStatement("SELECT " + DB.gu_bug + " FROM "
612: + DB.k_bugs + " b WHERE b." + DB.pg_bug
613: + "=? AND EXISTS " + " (SELECT p." + DB.gu_project
614: + " FROM " + DB.k_projects + " p WHERE b."
615: + DB.gu_project + "=p." + DB.gu_project + " AND p."
616: + DB.gu_owner + "=?)", ResultSet.TYPE_FORWARD_ONLY,
617: ResultSet.CONCUR_READ_ONLY);
618: oStmt.setInt(1, iBugPg);
619: oStmt.setString(2, sWorkArea);
620: oRSet = oStmt.executeQuery();
621: if (oRSet.next())
622: sRetVal = oRSet.getString(1);
623: else
624: sRetVal = null;
625: oRSet.close();
626: oStmt.close();
627:
628: return sRetVal;
629: } // getIdFromPg
630:
631: // **********************************************************
632: // Constantes Publicas
633:
634: public static final short ClassId = 82;
635: }
|