001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/announcement/tags/sakai_2-4-1/announcement-impl/impl/src/java/org/sakaiproject/announcement/impl/DbAnnouncementService.java $
003: * $Id: DbAnnouncementService.java 8167 2006-04-22 23:55:59Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.announcement.impl;
021:
022: // import
023: import java.sql.Connection;
024: import java.sql.ResultSet;
025: import java.util.List;
026: import java.util.Stack;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.sakaiproject.authz.api.AuthzGroup;
031: import org.sakaiproject.authz.api.GroupNotDefinedException;
032: import org.sakaiproject.authz.api.Role;
033: import org.sakaiproject.authz.cover.AuthzGroupService;
034: import org.sakaiproject.db.api.SqlReader;
035: import org.sakaiproject.db.api.SqlService;
036: import org.sakaiproject.entity.api.Reference;
037: import org.sakaiproject.entity.api.ResourceProperties;
038: import org.sakaiproject.message.api.Message;
039: import org.sakaiproject.message.api.MessageChannel;
040: import org.sakaiproject.message.api.MessageChannelEdit;
041: import org.sakaiproject.message.api.MessageEdit;
042: import org.sakaiproject.time.api.Time;
043: import org.sakaiproject.util.BaseDbDoubleStorage;
044: import org.sakaiproject.util.StorageUser;
045: import org.sakaiproject.util.Xml;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048:
049: /**
050: * <p>
051: * DbAnnouncementService fills out the BaseAnnouncementService with a database implementation.
052: * </p>
053: * <p>
054: * The sql scripts in src/sql/chef_announcement.sql must be run on the database.
055: * </p>
056: */
057: public class DbAnnouncementService extends BaseAnnouncementService {
058: /** Our logger. */
059: private static Log M_log = LogFactory
060: .getLog(DbAnnouncementService.class);
061:
062: /** The name of the db table holding announcement channels. */
063: protected String m_cTableName = "ANNOUNCEMENT_CHANNEL";
064:
065: /** The name of the db table holding announcement messages. */
066: protected String m_rTableName = "ANNOUNCEMENT_MESSAGE";
067:
068: /** If true, we do our locks in the remote database, otherwise we do them here. */
069: protected boolean m_locksInDb = true;
070:
071: protected static final String[] FIELDS = { "MESSAGE_DATE", "OWNER",
072: "DRAFT", "PUBVIEW" };
073:
074: /**********************************************************************************************************************************************************************************************************************************************************
075: * Constructors, Dependencies and their setter methods
076: *********************************************************************************************************************************************************************************************************************************************************/
077:
078: /** Dependency: SqlService */
079: protected SqlService m_sqlService = null;
080:
081: /**
082: * Dependency: SqlService.
083: *
084: * @param service
085: * The SqlService.
086: */
087: public void setSqlService(SqlService service) {
088: m_sqlService = service;
089: }
090:
091: /**
092: * Configuration: set the table name for the container.
093: *
094: * @param path
095: * The table name for the container.
096: */
097: public void setContainerTableName(String name) {
098: m_cTableName = name;
099: }
100:
101: /**
102: * Configuration: set the table name for the resource.
103: *
104: * @param path
105: * The table name for the resource.
106: */
107: public void setResourceTableName(String name) {
108: m_rTableName = name;
109: }
110:
111: /**
112: * Configuration: set the locks-in-db
113: *
114: * @param path
115: * The storage path.
116: */
117: public void setLocksInDb(String value) {
118: m_locksInDb = Boolean.valueOf(value).booleanValue();
119: }
120:
121: /** Set if we are to run the to-draft/owner conversion. */
122: protected boolean m_convertToDraft = false;
123:
124: /**
125: * Configuration: run the to-draft/owner conversion
126: *
127: * @param value
128: * The conversion desired value.
129: */
130: public void setConvertDraft(String value) {
131: m_convertToDraft = Boolean.valueOf(value).booleanValue();
132: }
133:
134: /** Set if we are to run the to-pubview conversion. */
135: protected boolean m_convertToPubView = false;
136:
137: /**
138: * Configuration: run the to-pubview conversion
139: *
140: * @param value
141: * The conversion desired value.
142: */
143: public void setConvertPubView(String value) {
144: m_convertToPubView = Boolean.valueOf(value).booleanValue();
145: }
146:
147: /** Configuration: to run the ddl on init or not. */
148: protected boolean m_autoDdl = false;
149:
150: /**
151: * Configuration: to run the ddl on init or not.
152: *
153: * @param value
154: * the auto ddl value.
155: */
156: public void setAutoDdl(String value) {
157: m_autoDdl = new Boolean(value).booleanValue();
158: }
159:
160: /**********************************************************************************************************************************************************************************************************************************************************
161: * Init and Destroy
162: *********************************************************************************************************************************************************************************************************************************************************/
163:
164: /**
165: * Final initialization, once all dependencies are set.
166: */
167: public void init() {
168: try {
169: // if we are auto-creating our schema, check and create
170: if (m_autoDdl) {
171: m_sqlService.ddl(this .getClass().getClassLoader(),
172: "sakai_announcement");
173: }
174:
175: super .init();
176:
177: M_log.info("init(): tables: " + m_cTableName + " "
178: + m_rTableName + " locks-in-db: " + m_locksInDb);
179:
180: // convert draft?
181: if (m_convertToDraft) {
182: m_convertToDraft = false;
183: convertToDraft();
184: }
185:
186: // convert pubview?
187: if (m_convertToPubView) {
188: m_convertToPubView = false;
189: convertToPubView();
190: }
191:
192: } catch (Throwable t) {
193: M_log.warn("init(): ", t);
194: }
195: }
196:
197: /**********************************************************************************************************************************************************************************************************************************************************
198: * BaseMessageService extensions
199: *********************************************************************************************************************************************************************************************************************************************************/
200:
201: /**
202: * Construct a Storage object.
203: *
204: * @return The new storage object.
205: */
206: protected Storage newStorage() {
207: return new DbStorage(this );
208:
209: } // newStorage
210:
211: /**********************************************************************************************************************************************************************************************************************************************************
212: * Storage implementation
213: *********************************************************************************************************************************************************************************************************************************************************/
214:
215: protected class DbStorage extends BaseDbDoubleStorage implements
216: Storage {
217: /**
218: * Construct.
219: *
220: * @param user
221: * The StorageUser class to call back for creation of Resource and Edit objects.
222: */
223: public DbStorage(StorageUser user) {
224: super (m_cTableName, "CHANNEL_ID", m_rTableName,
225: "MESSAGE_ID", "CHANNEL_ID", "MESSAGE_DATE",
226: "OWNER", "DRAFT", "PUBVIEW", FIELDS, m_locksInDb,
227: "channel", "message", user, m_sqlService);
228:
229: } // DbStorage
230:
231: /** Channels * */
232:
233: public boolean checkChannel(String ref) {
234: return super .getContainer(ref) != null;
235: }
236:
237: public MessageChannel getChannel(String ref) {
238: return (MessageChannel) super .getContainer(ref);
239: }
240:
241: public List getChannels() {
242: return super .getAllContainers();
243: }
244:
245: public MessageChannelEdit putChannel(String ref) {
246: return (MessageChannelEdit) super .putContainer(ref);
247: }
248:
249: public MessageChannelEdit editChannel(String ref) {
250: return (MessageChannelEdit) super .editContainer(ref);
251: }
252:
253: public void commitChannel(MessageChannelEdit edit) {
254: super .commitContainer(edit);
255: }
256:
257: public void cancelChannel(MessageChannelEdit edit) {
258: super .cancelContainer(edit);
259: }
260:
261: public void removeChannel(MessageChannelEdit edit) {
262: super .removeContainer(edit);
263: }
264:
265: public List getChannelIdsMatching(String root) {
266: return super .getContainerIdsMatching(root);
267: }
268:
269: /** messages * */
270:
271: public boolean checkMessage(MessageChannel channel, String id) {
272: return super .checkResource(channel, id);
273: }
274:
275: public Message getMessage(MessageChannel channel, String id) {
276: return (Message) super .getResource(channel, id);
277: }
278:
279: public List getMessages(MessageChannel channel) {
280: return super .getAllResources(channel);
281: }
282:
283: public MessageEdit putMessage(MessageChannel channel, String id) {
284: return (MessageEdit) super .putResource(channel, id, null);
285: }
286:
287: public MessageEdit editMessage(MessageChannel channel, String id) {
288: return (MessageEdit) super .editResource(channel, id);
289: }
290:
291: public void commitMessage(MessageChannel channel,
292: MessageEdit edit) {
293: super .commitResource(channel, edit);
294: }
295:
296: public void cancelMessage(MessageChannel channel,
297: MessageEdit edit) {
298: super .cancelResource(channel, edit);
299: }
300:
301: public void removeMessage(MessageChannel channel,
302: MessageEdit edit) {
303: super .removeResource(channel, edit);
304: }
305:
306: public List getMessages(MessageChannel channel, Time afterDate,
307: int limitedToLatest, String draftsForId,
308: boolean pubViewOnly) {
309: return super .getResources(channel, afterDate,
310: limitedToLatest, draftsForId, pubViewOnly);
311: }
312:
313: } // DbStorage
314:
315: /**
316: * fill in the draft and owner db fields
317: */
318: protected void convertToDraft() {
319: M_log.info("convertToDraft");
320:
321: try {
322: // get a connection
323: final Connection connection = m_sqlService
324: .borrowConnection();
325: boolean wasCommit = connection.getAutoCommit();
326: connection.setAutoCommit(false);
327:
328: // read all message records that need conversion
329: String sql = "select CHANNEL_ID, MESSAGE_ID, XML from "
330: + m_rTableName /* + " where OWNER is null" */;
331: m_sqlService.dbRead(connection, sql, null, new SqlReader() {
332: private int count = 0;
333:
334: public Object readSqlResultRecord(ResultSet result) {
335: try {
336: // create the Resource from the db xml
337: String channelId = result.getString(1);
338: String messageId = result.getString(2);
339: String xml = result.getString(3);
340:
341: // read the xml
342: Document doc = Xml.readDocumentFromString(xml);
343:
344: // verify the root element
345: Element root = doc.getDocumentElement();
346: if (!root.getTagName().equals("message")) {
347: M_log
348: .warn("convertToDraft(): XML root element not message: "
349: + root.getTagName());
350: return null;
351: }
352: Message m = new BaseMessageEdit(null, root);
353:
354: // pick up the fields
355: String owner = m.getHeader().getFrom().getId();
356: boolean draft = m.getHeader().getDraft();
357:
358: // update
359: String update = "update "
360: + m_rTableName
361: + " set OWNER = ?, DRAFT = ? where CHANNEL_ID = ? and MESSAGE_ID = ?";
362: Object fields[] = new Object[4];
363: fields[0] = owner;
364: fields[1] = (draft ? "1" : "0");
365: fields[2] = channelId;
366: fields[3] = messageId;
367: boolean ok = m_sqlService.dbWrite(connection,
368: update, fields);
369:
370: if (!ok)
371: M_log
372: .info("convertToDraft: channel: "
373: + channelId + " message: "
374: + messageId + " owner: "
375: + owner + " draft: "
376: + draft + " ok: " + ok);
377:
378: count++;
379: if (count % 100 == 0) {
380: M_log.info("convertToDraft: " + count);
381: }
382: return null;
383: } catch (Throwable ignore) {
384: return null;
385: }
386: }
387: });
388:
389: connection.commit();
390: connection.setAutoCommit(wasCommit);
391: m_sqlService.returnConnection(connection);
392: } catch (Throwable t) {
393: M_log.warn("convertToDraft: failed: " + t);
394: }
395:
396: M_log.info("convertToDraft: done");
397: }
398:
399: /**
400: * fill in the pubview db fields
401: */
402: protected void convertToPubView() {
403: M_log.info("convertToPubView");
404:
405: try {
406: // get a connection
407: final Connection connection = m_sqlService
408: .borrowConnection();
409: boolean wasCommit = connection.getAutoCommit();
410: connection.setAutoCommit(false);
411:
412: // read all message records that need conversion
413: String sql = "select CHANNEL_ID, MESSAGE_ID, XML, PUBVIEW from "
414: + m_rTableName;
415: m_sqlService.dbRead(connection, sql, null, new SqlReader() {
416: public Object readSqlResultRecord(ResultSet result) {
417: try {
418: // create the Resource from the db xml
419: String channelId = result.getString(1);
420: String messageId = result.getString(2);
421: String xml = result.getString(3);
422: String pubViewSetting = result.getString(4);
423:
424: // read the xml
425: Document doc = Xml.readDocumentFromString(xml);
426:
427: // verify the root element
428: Element root = doc.getDocumentElement();
429: if (!root.getTagName().equals("message")) {
430: M_log
431: .warn("convertToPubView(): XML root element not message: "
432: + root.getTagName());
433: return null;
434: }
435: BaseMessageEdit m = new BaseMessageEdit(null,
436: root);
437:
438: // check if the record already has pub view set in the properties
439: boolean pubview = false;
440: if (m.getProperties().getProperty(
441: ResourceProperties.PROP_PUBVIEW) != null) {
442: // pub view set in properties and in db indicates all is well with this one
443: if ("1".equals(pubViewSetting)) {
444: return null;
445: }
446:
447: // having the property overrides any realm setting...
448: pubview = true;
449: }
450:
451: // if we don't know pubview from the props, check the realm
452: else {
453: // m.getReference() won't work cause we didn't give it its channel...
454: Reference channel = m_entityManager
455: .newReference(channelId);
456: String ref = messageReference(channel
457: .getContext(), channel.getId(), m
458: .getId());
459: pubview = getPubView(ref);
460:
461: // if the pubview setting matches the db, and it's false, all is well
462: if ((!pubview)
463: && ("0".equals(pubViewSetting))) {
464: return null;
465: }
466: }
467:
468: // update those that have no pubview
469: if (!pubview) {
470: String update = "update "
471: + m_rTableName
472: + " set PUBVIEW = ? where CHANNEL_ID = ? and MESSAGE_ID = ?";
473: Object fields[] = new Object[3];
474: fields[0] = "0";
475: fields[1] = channelId;
476: fields[2] = messageId;
477: boolean ok = m_sqlService.dbWrite(
478: connection, update, fields);
479:
480: if (!ok)
481: M_log
482: .info("convertToPubView: channel: "
483: + channelId
484: + " message: "
485: + messageId
486: + " pubview: "
487: + pubview
488: + " ok: "
489: + ok);
490: }
491:
492: // update those that have pubview
493: else {
494: // set the property
495: m.getPropertiesEdit().addProperty(
496: ResourceProperties.PROP_PUBVIEW,
497: Boolean.TRUE.toString());
498:
499: // form updated XML
500: doc = Xml.createDocument();
501: m.toXml(doc, new Stack());
502: xml = Xml.writeDocumentToString(doc);
503:
504: String update = "update "
505: + m_rTableName
506: + " set PUBVIEW = ?, XML = ? where CHANNEL_ID = ? and MESSAGE_ID = ?";
507: Object fields[] = new Object[4];
508: fields[0] = "1";
509: fields[1] = xml;
510: fields[2] = channelId;
511: fields[3] = messageId;
512: boolean ok = m_sqlService.dbWrite(
513: connection, update, fields);
514:
515: if (!ok)
516: M_log
517: .info("convertToPubView: channel: "
518: + channelId
519: + " message: "
520: + messageId
521: + " pubview: "
522: + pubview
523: + " ok: "
524: + ok);
525: }
526:
527: return null;
528: } catch (Throwable ignore) {
529: return null;
530: }
531: }
532: });
533:
534: connection.commit();
535: connection.setAutoCommit(wasCommit);
536: m_sqlService.returnConnection(connection);
537: } catch (Throwable t) {
538: M_log.warn("convertToPubView: failed: " + t);
539: }
540:
541: M_log.info("convertToPubView: done");
542: }
543:
544: /**
545: * Does this resource support public view? (Support for the conversion)
546: *
547: * @param ref
548: * The resource reference
549: * @return true if this resource supports public view, false if not.
550: */
551: protected boolean getPubView(String ref) {
552: // get the realm
553: try {
554: AuthzGroup realm = AuthzGroupService.getAuthzGroup(ref);
555:
556: // if the announcement realm has "pubview" role, then the announcement is publicly viewable
557: Role pubview = realm.getRole("pubview");
558: if (pubview != null)
559: return true;
560:
561: // if the announcement realm has the anonymous role and the anonymous
562: // role contains content.read then the announcement is publicly viewable.
563: // (Because the AuthzGroupService converts pubview role (in a realm)
564: // to just .anon role with content.read function)
565: Role anon = realm.getRole(".anon");
566:
567: if (anon != null
568: && anon.getAllowedFunctions().contains(
569: "content.read")) {
570: return true;
571: }
572:
573: return false;
574:
575: // Set anon = realm.getAnonRoles();
576: // if (!anon.contains(pubview))
577: // return false;
578: //
579: // Set auth = realm.getAuthRoles();
580: // if (!auth.contains(pubview))
581: // return false;
582: } catch (GroupNotDefinedException e) {
583: // if no realm, no pub view
584: return false;
585: }
586: }
587: }
|