001: /*
002: * NEMESIS-FORUM.
003: * Copyright (C) 2002 David Laurent(lithium2@free.fr). All rights reserved.
004: *
005: * Copyright (c) 2000 The Apache Software Foundation. All rights reserved.
006: *
007: * Copyright (C) 2001 Yasna.com. All rights reserved.
008: *
009: * Copyright (C) 2000 CoolServlets.com. All rights reserved.
010: *
011: * NEMESIS-FORUM. is free software; you can redistribute it and/or
012: * modify it under the terms of the Apache Software License, Version 1.1,
013: * or (at your option) any later version.
014: *
015: * NEMESIS-FORUM core framework, NEMESIS-FORUM backoffice, NEMESIS-FORUM frontoffice
016: * application are parts of NEMESIS-FORUM and are distributed under
017: * same terms of licence.
018: *
019: *
020: * NEMESIS-FORUM includes software developed by the Apache Software Foundation (http://www.apache.org/)
021: * and software developed by CoolServlets.com (http://www.coolservlets.com).
022: * and software developed by Yasna.com (http://www.yasna.com).
023: *
024: */
025: package org.nemesis.forum.impl;
026:
027: import java.sql.Connection;
028: import java.sql.PreparedStatement;
029: import java.sql.ResultSet;
030: import java.sql.SQLException;
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.Map;
035: import java.util.Properties;
036: import java.util.Vector;
037:
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040: import org.nemesis.forum.Forum;
041: import org.nemesis.forum.ForumFactory;
042: import org.nemesis.forum.ForumThread;
043: import org.nemesis.forum.Message;
044: import org.nemesis.forum.User;
045: import org.nemesis.forum.event.ForumEvent;
046: import org.nemesis.forum.event.ForumListener;
047: import org.nemesis.forum.exception.ForumMessageNotFoundException;
048: import org.nemesis.forum.exception.UnauthorizedException;
049: import org.nemesis.forum.exception.UserNotFoundException;
050: import org.nemesis.forum.util.StringUtils;
051: import org.nemesis.forum.util.cache.CacheSizes;
052: import org.nemesis.forum.util.cache.Cacheable;
053: import org.nemesis.forum.util.jdbc.DbConnectionManager;
054:
055: /**
056: * Database implementation of the ForumMessage interface. It stores messages
057: * in the Message database table, and message properties in JiveMessageProp
058: * table.
059: */
060: public final class DbForumMessage implements Message, Cacheable {
061: static protected Log log = LogFactory.getLog(DbForumMessage.class);
062: /** DATABASE QUERIES **/
063: private static final String LOAD_PROPERTIES = "SELECT name, propValue FROM yazdMessageProp WHERE messageID=?";
064: private static final String DELETE_PROPERTIES = "DELETE FROM yazdMessageProp WHERE messageID=?";
065: private static final String INSERT_PROPERTY = "INSERT INTO yazdMessageProp(messageID,name,propValue) VALUES(?,?,?)";
066:
067: private static final String LOAD_MESSAGE = "SELECT userID, creationDate, modifiedDate, subject, body, threadID,approved FROM "
068: + "yazdMessage WHERE messageID=?";
069: private static final String INSERT_MESSAGE = "INSERT INTO yazdMessage(messageID, threadID,creationDate,modifiedDate,userID,"
070: + "subject,body,approved) VALUES(?,?,?,?,?,?,?,?)";
071: private static final String SAVE_MESSAGE = "UPDATE yazdMessage SET userID=?, subject=?, body=?, creationDate=?, modifiedDate=?,approved=? "
072: + "WHERE messageID=?";
073:
074: private static final String GET_FORUM_BY_THREAD = "SELECT forumID FROM yazdThread where threadID=?";
075:
076: private int id = -1;
077: private java.util.Date creationDate;
078: private java.util.Date modifiedDate;
079: private String subject = "";
080: private String body = "";
081: private int userID;
082: private int threadID;
083: private Map properties;
084: private Object propertyLock = new Object();
085: private ForumFactory factory;
086: private ForumThread thread = null;
087: protected boolean approved;//ICI
088:
089: /**
090: * Indicates if the object is ready to be saved or not. An object is not
091: * ready to be saved if it has just been created and has not yet been added
092: * to its container. For example, a message added to a thread, etc.
093: */
094: private boolean isReadyToSave = false;
095:
096: /**
097: * Creates a new DbForumMessage object.
098: */
099: protected DbForumMessage(User user, boolean approved,
100: ForumFactory factory) {
101: this .id = DbSequenceManager.nextID("ForumMessage");
102: long now = System.currentTimeMillis();
103: creationDate = new java.util.Date(now);
104: modifiedDate = new java.util.Date(now);
105: this .userID = user.getID();
106: this .factory = factory;
107: this .approved = approved;
108: properties = Collections.synchronizedMap(new HashMap());
109: }
110:
111: /**
112: * Loads the specified DbForumMessage by its message id.
113: */
114: protected DbForumMessage(int id, DbForumFactory factory)
115: throws ForumMessageNotFoundException {
116: this .id = id;
117: this .factory = factory;
118: loadFromDb();
119: loadProperties();
120: isReadyToSave = true;
121: }
122:
123: //FROM THE FORUMMESSAGE INTERFACE//
124: public boolean isApproved() {
125: return approved;
126: }
127:
128: public void setApproved(boolean approved)
129: throws UnauthorizedException {
130: this .approved = approved;
131: //Only save to the db if the object is read
132: if (!isReadyToSave) {
133: return;
134: }
135: saveToDb();
136: }
137:
138: public int getID() {
139: return id;
140: }
141:
142: public java.util.Date getCreationDate() {
143: return creationDate;
144: }
145:
146: public void setCreationDate(java.util.Date creationDate)
147: throws UnauthorizedException {
148: this .creationDate = creationDate;
149: //Only save to the db if the object is read
150: if (!isReadyToSave) {
151: return;
152: }
153: saveToDb();
154: }
155:
156: public java.util.Date getModifiedDate() {
157: return modifiedDate;
158: }
159:
160: public void setModifiedDate(java.util.Date modifiedDate)
161: throws UnauthorizedException {
162: this .modifiedDate = modifiedDate;
163: //Only save to the db if the object is read
164: if (!isReadyToSave) {
165: return;
166: }
167: saveToDb();
168: }
169:
170: public String getSubject() {
171: return subject;
172: }
173:
174: public String getUnfilteredSubject() {
175: return subject;
176: }
177:
178: public void setSubject(String subject) throws UnauthorizedException {
179: this .subject = subject;
180: //Only save to the db if the object is read
181: if (!isReadyToSave) {
182: return;
183: }
184: //Update modifiedDate to the current time.
185: modifiedDate.setTime(System.currentTimeMillis());
186: saveToDb();
187: }
188:
189: public String getBody() {
190: return body;
191: }
192:
193: public String getUnfilteredBody() {
194: return body;
195: }
196:
197: public void setBody(String body) throws UnauthorizedException {
198: this .body = body;
199: //Only save to the db if the object is read
200: if (!isReadyToSave) {
201: return;
202: }
203: //Update modifiedDate to the current time.
204: modifiedDate.setTime(System.currentTimeMillis());
205: saveToDb();
206: }
207:
208: public User getUser() {
209: User user = null;
210: try {
211: if (userID == -1) {
212: user = factory.getProfileManager().getAnonymousUser();
213: } else {
214: user = factory.getProfileManager().getUser(userID);
215: }
216: } catch (UserNotFoundException unfe) {
217: log.error("", unfe);
218: }
219: return user;
220: }
221:
222: public String getProperty(String name) {
223: //For security reasons, pass through the HTML filter.
224: return StringUtils
225: .escapeHTMLTags((String) properties.get(name));
226: }
227:
228: public String getUnfilteredProperty(String name) {
229: return (String) properties.get(name);
230: }
231:
232: public void setProperty(String name, String value) {
233: properties.put(name, value);
234: //Only save to the db if the object is read
235: if (!isReadyToSave) {
236: return;
237: }
238: saveProperties();
239: }
240:
241: public Iterator propertyNames() {
242: return Collections.unmodifiableSet(properties.keySet())
243: .iterator();
244: }
245:
246: public boolean isAnonymous() {
247: return (userID == -1);
248: }
249:
250: public ForumThread getForumThread() {
251: if (thread != null) {
252: return thread;
253: }
254: //Load the thread since this is the first time the method has been
255: //called.
256: else {
257: //First, we need a handle on the parent Forum object based
258: //on the threadID.
259: int forumID = -1;
260: Connection con = null;
261: PreparedStatement pstmt = null;
262: try {
263: con = DbConnectionManager.getConnection();
264: pstmt = con.prepareStatement(GET_FORUM_BY_THREAD);
265: pstmt.setInt(1, threadID);
266: ResultSet rs = pstmt.executeQuery();
267: if (rs.next()) {
268: forumID = rs.getInt("forumID");
269: }
270: } catch (SQLException sqle) {
271: log.error("", sqle);
272: } finally {
273: try {
274: pstmt.close();
275: } catch (Exception e) {
276: log.error("", e);
277: }
278: try {
279: con.close();
280: } catch (Exception e) {
281: log.error("", e);
282: }
283: }
284: //If the forumID for the message is less than 1, we have problems.
285: //Print a warning and return null
286: if (forumID < 1) {
287: log
288: .error("WARNING: forumID of "
289: + forumID
290: + " found for message "
291: + id
292: + " in DbForumMessage.getForumThread()."
293: + " You may wish to delete the message from your database.");
294: return null;
295: }
296:
297: Forum forum = null;
298: ForumThread thread = null;
299: try {
300: forum = factory.getForum(forumID);
301: //Now, get the thread
302: thread = forum.getThread(threadID);
303: } catch (Exception e) {
304: log.error("", e);
305: return null;
306: }
307: this .thread = thread;
308: return thread;
309: }
310: }
311:
312: public boolean hasPermission(int type) {
313: return true;
314: }
315:
316: //FROM CACHEABLE INTERFACE//
317:
318: public int getSize() {
319: //Approximate the size of the object in bytes by calculating the size
320: //of each field.
321: int size = 0;
322: size += CacheSizes.sizeOfObject(); //overhead of object
323: size += CacheSizes.sizeOfInt(); //id
324: size += CacheSizes.sizeOfString(subject); //subject
325: size += CacheSizes.sizeOfString(body); //body
326: size += CacheSizes.sizeOfDate(); //creation date
327: size += CacheSizes.sizeOfDate(); //modified date
328: size += CacheSizes.sizeOfInt(); //userID
329: size += CacheSizes.sizeOfInt(); //threadID
330: size += CacheSizes.sizeOfMap(properties); //map object
331: size += CacheSizes.sizeOfObject(); //property lock
332: size += CacheSizes.sizeOfObject(); //ref to factory
333:
334: return size;
335: }
336:
337: //OTHER METHODS//
338:
339: /**
340: * Returns a String representation of the message object using the subject.
341: *
342: * @return a String representation of the ForumMessage object.
343: */
344: public String toString() {
345: return subject;
346: }
347:
348: public int hashCode() {
349: return id;
350: }
351:
352: public boolean equals(Object object) {
353: if (this == object) {
354: return true;
355: }
356: if (object != null && object instanceof DbForumMessage) {
357: return id == ((DbForumMessage) object).getID();
358: } else {
359: return false;
360: }
361: }
362:
363: /**
364: * Loads message properties from the database.
365: */
366: private void loadProperties() {
367: synchronized (propertyLock) {
368: Properties newProps = new Properties();
369: Connection con = null;
370: PreparedStatement pstmt = null;
371: try {
372: con = DbConnectionManager.getConnection();
373: pstmt = con.prepareStatement(LOAD_PROPERTIES);
374: pstmt.setInt(1, id);
375: ResultSet rs = pstmt.executeQuery();
376: while (rs.next()) {
377: String name = rs.getString("name");
378: String value = rs.getString("propValue");
379: newProps.put(name, value);
380: }
381: } catch (SQLException sqle) {
382: log.error("Error in DbForumMessage:loadProperties():",
383: sqle);
384:
385: } finally {
386: try {
387: pstmt.close();
388: } catch (Exception e) {
389: log.error("", e);
390: }
391: try {
392: con.close();
393: } catch (Exception e) {
394: log.error("", e);
395: }
396: }
397: this .properties = newProps;
398: }
399: }
400:
401: /**
402: * Saves message properties to the database.
403: */
404: private void saveProperties() {
405: synchronized (propertyLock) {
406: Connection con = null;
407: PreparedStatement pstmt = null;
408: try {
409: con = DbConnectionManager.getConnection();
410: //Delete all old values.
411: pstmt = con.prepareStatement(DELETE_PROPERTIES);
412: pstmt.setInt(1, id);
413: pstmt.execute();
414: pstmt.close();
415: //Now insert new values.
416: pstmt = con.prepareStatement(INSERT_PROPERTY);
417: Iterator iter = properties.keySet().iterator();
418: while (iter.hasNext()) {
419: String name = (String) iter.next();
420: String value = (String) properties.get(name);
421: pstmt.setInt(1, id);
422: pstmt.setString(2, name);
423: pstmt.setString(3, value);
424: pstmt.executeUpdate();
425: }
426: } catch (SQLException sqle) {
427: log.error("", sqle);
428: } finally {
429: try {
430: pstmt.close();
431: } catch (Exception e) {
432: log.error("", e);
433: }
434: try {
435: con.close();
436: } catch (Exception e) {
437: log.error("", e);
438: }
439: }
440: }
441: }
442:
443: /**
444: * Loads message and user data from the database.
445: */
446: private void loadFromDb() throws ForumMessageNotFoundException {
447: // Based on the id in the object, get the message data from the database.
448: Connection con = null;
449: PreparedStatement pstmt = null;
450: try {
451: con = DbConnectionManager.getConnection();
452: pstmt = con.prepareStatement(LOAD_MESSAGE);
453: pstmt.setInt(1, id);
454: ResultSet rs = pstmt.executeQuery();
455: if (!rs.next()) {
456: throw new ForumMessageNotFoundException("Message " + id
457: + " could not be loaded from the database.");
458: }
459: //Get the query results. We use int indexes into the ResultSet
460: //because it is slightly faster. Care should be taken so that the
461: //SQL query is not modified without modifying these indexes.
462: this .userID = rs.getInt(1);
463: //We trim() the dates before trying to parse them because some
464: //databases pad with extra characters when returning the data.
465: this .creationDate = new java.util.Date(Long.parseLong(rs
466: .getString(2).trim()));
467: this .modifiedDate = new java.util.Date(Long.parseLong(rs
468: .getString(3).trim()));
469: this .subject = rs.getString(4);
470: this .body = rs.getString(5);
471: this .threadID = rs.getInt(6);
472: this .approved = rs.getInt(7) == 1;
473: } catch (SQLException sqle) {
474: throw new ForumMessageNotFoundException("Message of id "
475: + id + " was not found in the database.");
476: } catch (NumberFormatException nfe) {
477: log
478: .error("WARNING: In DbForumMessage.loadFromDb() -- there "
479: + "was an error parsing the dates returned from the database. Ensure "
480: + "that they're being stored correctly.");
481: } finally {
482: try {
483: pstmt.close();
484: } catch (Exception e) {
485: log.error("", e);
486: }
487: try {
488: con.close();
489: } catch (Exception e) {
490: log.error("", e);
491: }
492: }
493: }
494:
495: /**
496: * Inserts a new message into the database. A connection object must
497: * be passed in. The connection must be open when passed in, and will
498: * remain open when passed back. This method allows us to make insertions
499: * be transactional.
500: *
501: * @param con an open Connection used to insert the thread to the db.
502: * @param thread the ForumThread the message is being added to.
503: */
504: public void insertIntoDb(Connection con, ForumThread thread)
505: throws SQLException {
506: //Set the message threadID to the thread that the message is being
507: //added to.
508: this .threadID = thread.getID();
509: PreparedStatement pstmt = con.prepareStatement(INSERT_MESSAGE);
510: pstmt.setInt(1, id);
511: pstmt.setInt(2, threadID);
512: pstmt.setString(3, Long.toString(creationDate.getTime()));
513: pstmt.setString(4, Long.toString(modifiedDate.getTime()));
514: pstmt.setInt(5, userID);
515: pstmt.setString(6, subject);
516: pstmt.setString(7, body);
517: pstmt.setInt(8, approved ? 1 : 0);
518: pstmt.executeUpdate();
519: pstmt.close();
520:
521: //We're done inserting the message, so now save any extended
522: //properties to the database.
523: saveProperties();
524:
525: ForumEvent event = new ForumEvent(this );
526: for (int i = 0; i < listeners.size(); i++) {
527: ((ForumListener) listeners.get(i)).objectCreated(event);
528: }
529:
530: //since we're done inserting the object to the database, it is ready
531: //for future insertions.
532: isReadyToSave = true;
533: }
534:
535: /**
536: * Saves message data to the database.
537: */
538: private synchronized void saveToDb() {
539: Connection con = null;
540: PreparedStatement pstmt = null;
541: try {
542: con = DbConnectionManager.getConnection();
543: pstmt = con.prepareStatement(SAVE_MESSAGE);
544: pstmt.setInt(1, userID);
545: pstmt.setString(2, subject);
546: pstmt.setString(3, body);
547: pstmt.setString(4, Long.toString(creationDate.getTime()));
548: pstmt.setString(5, Long.toString(modifiedDate.getTime()));
549: pstmt.setInt(6, approved ? 1 : 0);
550: pstmt.setInt(7, id);
551: pstmt.executeUpdate();
552:
553: ForumEvent event = new ForumEvent(this );
554: for (int i = 0; i < listeners.size(); i++) {
555: ((ForumListener) listeners.get(i))
556: .objectModified(event);
557: }
558:
559: } catch (SQLException sqle) {
560: log.error("SQLException in DbForumMessage:saveToDb()- "
561: + sqle);
562:
563: } finally {
564: try {
565: pstmt.close();
566: } catch (Exception e) {
567: log.error("", e);
568: }
569: try {
570: con.close();
571: } catch (Exception e) {
572: log.error("", e);
573: }
574: }
575: }
576:
577: // gestion des évenements
578: private static Vector listeners = new Vector();
579:
580: public static void addListener(ForumListener listener) {
581: listeners.add(listener);
582: }
583:
584: public static void removeListener(ForumListener listener) {
585: listeners.remove(listener);
586: }
587: }
|