/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/announcement/trunk/announcement-impl/impl/src/java/org/sakaiproject/announcement/impl/DbAnnouncementService.java $ * $Id: DbAnnouncementService.java 131324 2013-11-07 20:22:01Z matthew@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.announcement.impl; // import import java.sql.Connection; import java.sql.ResultSet; import java.util.List; import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.authz.api.AuthzGroup; import org.sakaiproject.authz.api.GroupNotDefinedException; import org.sakaiproject.authz.api.Role; import org.sakaiproject.authz.api.AuthzGroupService; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.message.api.Message; import org.sakaiproject.message.api.MessageChannel; import org.sakaiproject.message.api.MessageChannelEdit; import org.sakaiproject.message.api.MessageEdit; import org.sakaiproject.time.api.Time; import org.sakaiproject.util.BaseDbDoubleStorage; import org.sakaiproject.util.DoubleStorageUser; import org.sakaiproject.util.Xml; import org.sakaiproject.javax.Filter; import org.sakaiproject.javax.PagingPosition; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * <p> * DbAnnouncementService fills out the BaseAnnouncementService with a database implementation. * </p> * <p> * The sql scripts in src/sql/chef_announcement.sql must be run on the database. * </p> */ public class DbAnnouncementService extends BaseAnnouncementService { /** Our logger. */ private static Log M_log = LogFactory.getLog(DbAnnouncementService.class); /** The name of the db table holding announcement channels. */ protected String m_cTableName = "ANNOUNCEMENT_CHANNEL"; /** The name of the db table holding announcement messages. */ protected String m_rTableName = "ANNOUNCEMENT_MESSAGE"; /** If true, we do our locks in the remote database, otherwise we do them here. */ protected boolean m_locksInDb = true; protected static final String[] FIELDS = { "MESSAGE_DATE", "OWNER", "DRAFT", "PUBVIEW","MESSAGE_ORDER" }; /********************************************************************************************************************************************************************************************************************************************************** * Constructors, Dependencies and their setter methods *********************************************************************************************************************************************************************************************************************************************************/ /** Dependency: SqlService */ protected SqlService m_sqlService = null; /** * Dependency: SqlService. * * @param service * The SqlService. */ public void setSqlService(SqlService service) { m_sqlService = service; } private AuthzGroupService m_authzGroupService; public void setAuthzGroupService( org.sakaiproject.authz.api.AuthzGroupService authzGroupService) { this.m_authzGroupService = authzGroupService; super.setAuthzGroupService(authzGroupService); } /** * Configuration: set the table name for the container. * * @param path * The table name for the container. */ public void setContainerTableName(String name) { m_cTableName = name; } /** * Configuration: set the table name for the resource. * * @param path * The table name for the resource. */ public void setResourceTableName(String name) { m_rTableName = name; } /** * Configuration: set the locks-in-db * * @param path * The storage path. */ public void setLocksInDb(String value) { m_locksInDb = Boolean.valueOf(value).booleanValue(); } /** Set if we are to run the to-draft/owner conversion. */ protected boolean m_convertToDraft = false; /** * Configuration: run the to-draft/owner conversion * * @param value * The conversion desired value. */ public void setConvertDraft(String value) { m_convertToDraft = Boolean.valueOf(value).booleanValue(); } /** Set if we are to run the to-pubview conversion. */ protected boolean m_convertToPubView = false; /** * Configuration: run the to-pubview conversion * * @param value * The conversion desired value. */ public void setConvertPubView(String value) { m_convertToPubView = Boolean.valueOf(value).booleanValue(); } /** Configuration: to run the ddl on init or not. */ protected boolean m_autoDdl = false; /** * Configuration: to run the ddl on init or not. * * @param value * the auto ddl value. */ public void setAutoDdl(String value) { m_autoDdl = new Boolean(value).booleanValue(); } /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { try { // if we are auto-creating our schema, check and create if (m_autoDdl) { m_sqlService.ddl(this.getClass().getClassLoader(), "sakai_announcement"); } super.init(); M_log.info("init(): tables: " + m_cTableName + " " + m_rTableName + " locks-in-db: " + m_locksInDb); // convert draft? if (m_convertToDraft) { m_convertToDraft = false; convertToDraft(); } // convert pubview? if (m_convertToPubView) { m_convertToPubView = false; convertToPubView(); } } catch (Throwable t) { M_log.warn("init(): ", t); } } /********************************************************************************************************************************************************************************************************************************************************** * BaseMessage extensions *********************************************************************************************************************************************************************************************************************************************************/ /** * Construct a Storage object. * * @return The new storage object. */ protected Storage newStorage() { return new DbStorage(this); } // newStorage /********************************************************************************************************************************************************************************************************************************************************** * Storage implementation *********************************************************************************************************************************************************************************************************************************************************/ protected class DbStorage extends BaseDbDoubleStorage implements Storage { /** * Construct. * * @param user * The StorageUser class to call back for creation of Resource and Edit objects. */ public DbStorage(DoubleStorageUser user) { super(m_cTableName, "CHANNEL_ID", m_rTableName, "MESSAGE_ID", "CHANNEL_ID", "MESSAGE_DATE", "OWNER", "DRAFT", "PUBVIEW", FIELDS, m_locksInDb, "channel", "message", user, m_sqlService); } // DbStorage /** Channels * */ public boolean checkChannel(String ref) { return super.getContainer(ref) != null; } public MessageChannel getChannel(String ref) { return (MessageChannel) super.getContainer(ref); } public List getChannels() { return super.getAllContainers(); } public MessageChannelEdit putChannel(String ref) { return (MessageChannelEdit) super.putContainer(ref); } public MessageChannelEdit editChannel(String ref) { return (MessageChannelEdit) super.editContainer(ref); } public void commitChannel(MessageChannelEdit edit) { super.commitContainer(edit); } public void cancelChannel(MessageChannelEdit edit) { super.cancelContainer(edit); } public void removeChannel(MessageChannelEdit edit) { super.removeContainer(edit); } public List getChannelIdsMatching(String root) { return super.getContainerIdsMatching(root); } /** messages * */ public boolean checkMessage(MessageChannel channel, String id) { return super.checkResource(channel, id); } public Message getMessage(MessageChannel channel, String id) { return (Message) super.getResource(channel, id); } public List getMessages(MessageChannel channel) { return super.getAllResources(channel); } public MessageEdit putMessage(MessageChannel channel, String id) { return (MessageEdit) super.putResource(channel, id, null); } public MessageEdit editMessage(MessageChannel channel, String id) { return (MessageEdit) super.editResource(channel, id); } public void commitMessage(MessageChannel channel, MessageEdit edit) { super.commitResource(channel, edit); } public void cancelMessage(MessageChannel channel, MessageEdit edit) { super.cancelResource(channel, edit); } public void removeMessage(MessageChannel channel, MessageEdit edit) { super.removeResource(channel, edit); } public int getCount(MessageChannel channel) { return super.getCount(channel); } public int getCount(MessageChannel channel, Filter filter) { return super.getCount(channel, filter); } public List getMessages(MessageChannel channel, Filter filter, boolean asc, PagingPosition pager) { return super.getAllResources(channel, filter, null, asc, pager); } public List getMessages(MessageChannel channel, Time afterDate, int limitedToLatest, String draftsForId, boolean pubViewOnly) { return super.getResources(channel, afterDate, limitedToLatest, draftsForId, pubViewOnly); } } // DbStorage /** * fill in the draft and owner db fields */ protected void convertToDraft() { M_log.info("convertToDraft"); try { // get a connection final Connection connection = m_sqlService.borrowConnection(); boolean wasCommit = connection.getAutoCommit(); connection.setAutoCommit(false); // read all message records that need conversion String sql = "select CHANNEL_ID, MESSAGE_ID, XML from " + m_rTableName /* + " where OWNER is null" */; m_sqlService.dbRead(connection, sql, null, new SqlReader() { private int count = 0; public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml String channelId = result.getString(1); String messageId = result.getString(2); String xml = result.getString(3); // read the xml Document doc = Xml.readDocumentFromString(xml); // verify the root element Element root = doc.getDocumentElement(); if (!root.getTagName().equals("message")) { M_log.warn("convertToDraft(): XML root element not message: " + root.getTagName()); return null; } Message m = new BaseMessageEdit(null, root); // pick up the fields String owner = m.getHeader().getFrom().getId(); boolean draft = m.getHeader().getDraft(); // update String update = "update " + m_rTableName + " set OWNER = ?, DRAFT = ? where CHANNEL_ID = ? and MESSAGE_ID = ?"; Object fields[] = new Object[4]; fields[0] = owner; fields[1] = (draft ? "1" : "0"); fields[2] = channelId; fields[3] = messageId; boolean ok = m_sqlService.dbWrite(connection, update, fields); if (!ok) M_log.info("convertToDraft: channel: " + channelId + " message: " + messageId + " owner: " + owner + " draft: " + draft + " ok: " + ok); count++; if (count % 100 == 0) { M_log.info("convertToDraft: " + count); } return null; } catch (Throwable ignore) { return null; } } }); connection.commit(); connection.setAutoCommit(wasCommit); m_sqlService.returnConnection(connection); } catch (Throwable t) { M_log.warn("convertToDraft: failed: " + t); } M_log.info("convertToDraft: done"); } /** * fill in the pubview db fields */ protected void convertToPubView() { M_log.info("convertToPubView"); try { // get a connection final Connection connection = m_sqlService.borrowConnection(); boolean wasCommit = connection.getAutoCommit(); connection.setAutoCommit(false); // read all message records that need conversion String sql = "select CHANNEL_ID, MESSAGE_ID, XML, PUBVIEW from " + m_rTableName; m_sqlService.dbRead(connection, sql, null, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { // create the Resource from the db xml String channelId = result.getString(1); String messageId = result.getString(2); String xml = result.getString(3); String pubViewSetting = result.getString(4); // read the xml Document doc = Xml.readDocumentFromString(xml); // verify the root element Element root = doc.getDocumentElement(); if (!root.getTagName().equals("message")) { M_log.warn("convertToPubView(): XML root element not message: " + root.getTagName()); return null; } BaseMessageEdit m = new BaseMessageEdit(null, root); // check if the record already has pub view set in the properties boolean pubview = false; if (m.getProperties().getProperty(ResourceProperties.PROP_PUBVIEW) != null) { // pub view set in properties and in db indicates all is well with this one if ("1".equals(pubViewSetting)) { return null; } // having the property overrides any realm setting... pubview = true; } // if we don't know pubview from the props, check the realm else { // m.getReference() won't work cause we didn't give it its channel... Reference channel = m_entityManager.newReference(channelId); String ref = messageReference(channel.getContext(), channel.getId(), m.getId()); pubview = getPubView(ref); // if the pubview setting matches the db, and it's false, all is well if ((!pubview) && ("0".equals(pubViewSetting))) { return null; } } // update those that have no pubview if (!pubview) { String update = "update " + m_rTableName + " set PUBVIEW = ? where CHANNEL_ID = ? and MESSAGE_ID = ?"; Object fields[] = new Object[3]; fields[0] = "0"; fields[1] = channelId; fields[2] = messageId; boolean ok = m_sqlService.dbWrite(connection, update, fields); if (!ok) M_log.info("convertToPubView: channel: " + channelId + " message: " + messageId + " pubview: " + pubview + " ok: " + ok); } // update those that have pubview else { // set the property m.getPropertiesEdit().addProperty(ResourceProperties.PROP_PUBVIEW, Boolean.TRUE.toString()); // form updated XML doc = Xml.createDocument(); m.toXml(doc, new Stack()); xml = Xml.writeDocumentToString(doc); String update = "update " + m_rTableName + " set PUBVIEW = ?, XML = ? where CHANNEL_ID = ? and MESSAGE_ID = ?"; Object fields[] = new Object[4]; fields[0] = "1"; fields[1] = xml; fields[2] = channelId; fields[3] = messageId; boolean ok = m_sqlService.dbWrite(connection, update, fields); if (!ok) M_log.info("convertToPubView: channel: " + channelId + " message: " + messageId + " pubview: " + pubview + " ok: " + ok); } return null; } catch (Throwable ignore) { return null; } } }); connection.commit(); connection.setAutoCommit(wasCommit); m_sqlService.returnConnection(connection); } catch (Throwable t) { M_log.warn("convertToPubView: failed: " + t); } M_log.info("convertToPubView: done"); } /** * Does this resource support public view? (Support for the conversion) * * @param ref * The resource reference * @return true if this resource supports public view, false if not. */ protected boolean getPubView(String ref) { // get the realm try { AuthzGroup realm = m_authzGroupService.getAuthzGroup(ref); // if the announcement realm has "pubview" role, then the announcement is publicly viewable Role pubview = realm.getRole("pubview"); if (pubview != null) return true; // if the announcement realm has the anonymous role and the anonymous // role contains content.read then the announcement is publicly viewable. // (Because the AuthzGroupService converts pubview role (in a realm) // to just .anon role with content.read function) Role anon = realm.getRole(".anon"); if (anon != null && anon.getAllowedFunctions().contains("content.read")) { return true; } return false; // Set anon = realm.getAnonRoles(); // if (!anon.contains(pubview)) // return false; // // Set auth = realm.getAuthRoles(); // if (!auth.contains(pubview)) // return false; } catch (GroupNotDefinedException e) { // if no realm, no pub view return false; } } }