/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/mailarchive/trunk/mailarchive-impl/impl/src/java/org/sakaiproject/mailarchive/impl/DbMailArchiveService.java $ * $Id: DbMailArchiveService.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.mailarchive.impl; import java.sql.Connection; import java.sql.ResultSet; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.javax.Filter; import org.sakaiproject.javax.PagingPosition; 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.apache.commons.lang.StringUtils; import org.sakaiproject.util.Xml; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * <p> * DbMailArchiveService fills out the BaseMailArchiveService with a database implementation. * </p> * <p> * The sql scripts in src/sql/chef_mailarchive.sql must be run on the database. * </p> */ public class DbMailArchiveService extends BaseMailArchiveService { /** Our logger. */ private static Log M_log = LogFactory.getLog(DbMailArchiveService.class); /** The name of the db table holding mail archive channels. */ protected String m_cTableName = "MAILARCHIVE_CHANNEL"; /** The name of the db table holding mail archive messages. */ protected String m_rTableName = "MAILARCHIVE_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", "SUBJECT", "BODY"}; protected static final String[] SEARCH_FIELDS = { "OWNER", "SUBJECT", "BODY" }; /********************************************************************************************************************************************************************************************************************************************************** * 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; } /** * 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(); } /** 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 = Boolean.valueOf(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_mailarchive"); m_sqlService.ddl(this.getClass().getClassLoader(), "sakai_mailarchive_2_6_0"); } super.init(); M_log.info("init(): tables: " + m_cTableName + " " + m_rTableName + " locks-in-db: " + m_locksInDb); // convert? if (m_convertToDraft) { m_convertToDraft = false; convertToDraft(); } } 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, SEARCH_FIELDS, m_locksInDb, "channel", "message", user, m_sqlService); m_locksAreInTable = false; } // DbStorage /* matchXml - Optionaly do a pre-de-serialize match * * A call back to match before the XML is parsed and turned into a * Resource. If we can decide here - it is more efficient than * sending the XML through SAX. */ @Override public int matchXml(String xml, String search) { if (!xml.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")) return 0; /* * <?xml version="1.0" encoding="UTF-8"?> <message * body="Qm9keSAyMDA4MDEyNzIwMTM0MTkzMw==" * body-html="Qm9keSAyMDA4MDEyNzIwMTM0MTkzMw=="> <header * access="channel" date="20080127201341934" from="admin" * id="d978685c-8730-4975-b3ea-55fdf03e0e5a" * mail-date="20080127201341933" mail-from="from 20080127201341933" * subject="Subject 20080127201341933"/><properties/></message> */ String body = getXmlAttr(xml, "body"); String from = getXmlAttr(xml, "from"); String subject = getXmlAttr(xml, "subject"); if (body == null || from == null || subject == null) return 0; try { byte[] decoded = Base64.decodeBase64(body); // UTF-8 by default body = org.apache.commons.codec.binary.StringUtils.newStringUtf8(decoded); } catch (Exception e) { M_log.warn("Exception decoding message body: " + e); return 0; } if (StringUtils.containsIgnoreCase(subject, search) || StringUtils.containsIgnoreCase(from, search) || StringUtils.containsIgnoreCase(body, search)) { return 1; } return -1; } String getXmlAttr(String xml, String tagName) { String lookfor = tagName+"=\""; int ipos = xml.indexOf(lookfor); if ( ipos < 1 ) return null; ipos = ipos + lookfor.length(); int jpos = xml.indexOf("\"",ipos); if ( jpos < 1 || ipos > jpos ) return null; return xml.substring(ipos,jpos); } /** 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 List getMessages(MessageChannel channel,String search, boolean asc, PagingPosition pager) { return super.getAllResources(channel, null, search, asc, pager); } public int getCount(MessageChannel channel) { return super.getCount(channel); } public int getCount(MessageChannel channel, Filter filter) { return super.getCount(channel, filter); } 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 List getMessages(MessageChannel channel, Time afterDate, int limitedToLatest, String draftsForId, boolean pubViewOnly) { return super.getResources(channel, afterDate, limitedToLatest, draftsForId, pubViewOnly); } public List getMessages(MessageChannel channel, Filter filter,boolean asc, PagingPosition pager) { return super.getAllResources(channel,filter, null, asc, pager); } } // 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 (Exception ignore) { return null; } } }); connection.commit(); connection.setAutoCommit(wasCommit); m_sqlService.returnConnection(connection); } catch (Exception t) { M_log.warn("convertToDraft: failed: " + t); } M_log.info("convertToDraft: done"); } } // DbCachedMailArchiveService