/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/chat/trunk/chat-impl/impl/src/java/org/sakaiproject/chat2/model/impl/ChatEntityProducer.java $
* $Id: ChatEntityProducer.java 105079 2012-02-24 23:08:11Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2007, 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.chat2.model.impl;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.chat2.model.ChatChannel;
import org.sakaiproject.chat2.model.ChatManager;
import org.sakaiproject.chat2.model.ChatMessage;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.entity.api.Entity;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.entity.api.EntityNotDefinedException;
import org.sakaiproject.entity.api.EntityPermissionException;
import org.sakaiproject.entity.api.EntityProducer;
import org.sakaiproject.entity.api.EntityTransferrer;
import org.sakaiproject.entity.api.HttpAccess;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.time.cover.TimeService;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.util.StringUtil;
import org.sakaiproject.util.Web;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author chrismaurer
*
*/
public class ChatEntityProducer implements EntityProducer, EntityTransferrer {
protected final Log logger = LogFactory.getLog(getClass());
private EntityManager entityManager;
private ChatManager chatManager;
private static final String ARCHIVE_VERSION = "2.4"; // in case new features are added in future exports
private static final String VERSION_ATTR = "version";
private static final String CHANNEL_PROP = "channel";
private static final String SYNOPTIC_TOOL = "synoptic_tool";
private static final String NAME = "name";
private static final String VALUE = "value";
private static final String PROPERTIES = "properties";
private static final String PROPERTY = "property";
protected void init() throws Exception {
logger.info("init()");
try {
getEntityManager().registerEntityProducer(this, ChatManager.REFERENCE_ROOT);
}
catch (Exception e) {
logger.warn("Error registering Chat Entity Producer", e);
}
}
/**
* Destroy
*/
protected void destroy()
{
logger.info("destroy()");
}
/**
* {@inheritDoc}
*/
public String[] myToolIds()
{
String[] toolIds = { ChatManager.CHAT_TOOL_ID };
return toolIds;
}
/**
* Get the service name for this class
* @return
*/
protected String serviceName() {
return ChatEntityProducer.class.getName();
}
public ChatMessage getMessage(Reference reference) throws IdUnusedException, PermissionException {
return getChatManager().getMessage(reference.getId());
//return null;
}
public ChatChannel getChannel(Reference reference) throws IdUnusedException, PermissionException {
return getChatManager().getChatChannel(reference.getId());
//return null;
}
/**
* {@inheritDoc}
*/
public String archive(String siteId, Document doc, Stack stack, String archivePath, List attachments)
{
//prepare the buffer for the results log
StringBuilder results = new StringBuilder();
int channelCount = 0;
try
{
// start with an element with our very own (service) name
Element element = doc.createElement(serviceName());
element.setAttribute(VERSION_ATTR, ARCHIVE_VERSION);
((Element) stack.peek()).appendChild(element);
stack.push(element);
Element chat = doc.createElement(ChatManager.CHAT);
List channelList = getChatManager().getContextChannels(siteId, true);
if (channelList != null && !channelList.isEmpty())
{
Iterator channelIterator = channelList.iterator();
while (channelIterator.hasNext())
{
ChatChannel channel = (ChatChannel)channelIterator.next();
Element channelElement = channel.toXml(doc, stack);
chat.appendChild(channelElement);
channelCount++;
}
results.append("archiving " + getLabel() + ": (" + channelCount + ") channels archived successfully.\n");
}
else
{
results.append("archiving " + getLabel()
+ ": empty chat room archived.\n");
}
// archive the chat synoptic tool options
archiveSynopticOptions(siteId, doc, chat);
((Element) stack.peek()).appendChild(chat);
stack.push(chat);
stack.pop();
}
catch (Exception any)
{
logger.warn("archive: exception archiving service: " + serviceName());
}
stack.pop();
return results.toString();
}
/**
* try to add synoptic options for this tool to the archive, if they exist
* @param siteId
* @param doc
* @param element
*/
public void archiveSynopticOptions(String siteId, Document doc, Element element)
{
try
{
// archive the synoptic tool options
Site site = SiteService.getSite(siteId);
ToolConfiguration synTool = site.getToolForCommonId("sakai.synoptic." + getLabel());
Properties synProp = synTool.getPlacementConfig();
if (synProp != null && synProp.size() > 0) {
Element synElement = doc.createElement(SYNOPTIC_TOOL);
Element synProps = doc.createElement(PROPERTIES);
Set synPropSet = synProp.keySet();
Iterator propIter = synPropSet.iterator();
while (propIter.hasNext())
{
String propName = (String)propIter.next();
Element synPropEl = doc.createElement(PROPERTY);
synPropEl.setAttribute(NAME, propName);
synPropEl.setAttribute(VALUE, synProp.getProperty(propName));
synProps.appendChild(synPropEl);
}
synElement.appendChild(synProps);
element.appendChild(synElement);
}
}
catch (Exception e)
{
logger.warn("archive: exception archiving synoptic options for service: " + serviceName());
}
}
/**
* {@inheritDoc}
*/
public Entity getEntity(Reference ref)
{
// we could check that the type is one of the message services, but lets just assume it is so we don't need to know them here -ggolden
Entity rv = null;
try
{
// if this is a channel
if (ChatManager.REF_TYPE_CHANNEL.equals(ref.getSubType()))
{
rv = getChatManager().getChatChannel(ref.getReference());
}
// otherwise a message
else if (ChatManager.REF_TYPE_MESSAGE.equals(ref.getSubType()))
{
rv = getMessage(ref);
}
// else try {throw new Exception();} catch (Exception e) {M_log.warn("getResource(): unknown message ref subtype: " + m_subType + " in ref: " + m_reference, e);}
else
logger.warn("getEntity(): unknown message ref subtype: " + ref.getSubType() + " in ref: " + ref.getReference());
}
catch (NullPointerException e)
{
logger.warn("getEntity(): " + e);
} catch (IdUnusedException e) {
logger.warn("getEntity(): " + e);
} catch (PermissionException e) {
logger.warn("getEntity(): " + e);
}
return rv;
}
/**
* {@inheritDoc}
*/
public Collection getEntityAuthzGroups(Reference ref, String userId)
{
//TODO implement this
return null;
}
/**
* {@inheritDoc}
*/
public String getEntityDescription(Reference ref)
{
// we could check that the type is one of the message services, but lets just assume it is so we don't need to know them here -ggolden
String rv = "Message: " + ref.getReference();
try
{
// if this is a channel
if (ChatManager.REF_TYPE_CHANNEL.equals(ref.getSubType()))
{
ChatChannel channel = getChannel(ref);
rv = "Channel: " + channel.getId() + " (" + channel.getContext() + ")";
}
}
catch (PermissionException e)
{
}
catch (IdUnusedException e)
{
}
catch (NullPointerException e)
{
}
return rv;
}
/* (non-Javadoc)
* @see org.sakaiproject.entity.api.EntityProducer#getEntityResourceProperties(org.sakaiproject.entity.api.Reference)
*/
public ResourceProperties getEntityResourceProperties(Reference ref) {
// TODO Auto-generated method stub
return null;
}
/**
* {@inheritDoc}
*/
public String getEntityUrl(Reference ref)
{
// we could check that the type is one of the message services, but lets just assume it is so we don't need to know them here -ggolden
String url = null;
try
{
// if this is a channel
if (ChatManager.REF_TYPE_CHANNEL.equals(ref.getSubType()))
{
ChatChannel channel = getChannel(ref);
url = channel.getUrl();
}
// otherwise a message
else if (ChatManager.REF_TYPE_MESSAGE.equals(ref.getSubType()))
{
ChatMessage message = getMessage(ref);
url = message.getUrl();
}
else
logger.warn("getUrl(): unknown message ref subtype: " + ref.getSubType() + " in ref: " + ref.getReference());
}
catch (PermissionException e)
{
logger.warn("getUrl(): " + e);
}
catch (IdUnusedException e)
{
logger.warn("getUrl(): " + e);
}
catch (NullPointerException e)
{
logger.warn("getUrl(): " + e);
}
return url;
}
/**
* {@inheritDoc}
*/
public HttpAccess getHttpAccess()
{
return new HttpAccess()
{
public void handleAccess(HttpServletRequest req, HttpServletResponse res, Reference ref,
Collection copyrightAcceptedRefs) throws EntityPermissionException, EntityNotDefinedException
{
try
{
//TODO: Isn't there a better way to do this than build out the whole page here??
// We need to write to a temporary stream for better speed, plus
// so we can get a byte count. Internet Explorer has problems
// if we don't make the setContentLength() call.
ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
OutputStreamWriter sw = new OutputStreamWriter(outByteStream);
String skin = ServerConfigurationService.getString("skin.default");
String skinRepo = ServerConfigurationService.getString("skin.repo");
ChatMessage message = getMessage(ref);
String title = ref.getDescription();
//MessageHeader messageHead = message.getHeader();
//String date = messageHead.getDate().toStringLocalFullZ();
String date = TimeService.newTime(message.getMessageDate().getTime()).toStringLocalFullZ();
String from = UserDirectoryService.getUser(message.getOwner()).getDisplayName();
//String from = messageHead.getFrom().getDisplayName();
String groups = "";
//Collection gr = messageHead.getGroups();
//for (Iterator i = gr.iterator(); i.hasNext();)
//{
// groups += "<li>" + i.next() + "</li>";
//}
String body = Web.escapeHtml(message.getBody());
sw
.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n"
+ "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
+ "<link href=\"");
sw.write(skinRepo);
sw.write("/tool_base.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n" + "<link href=\"");
sw.write(skinRepo);
sw.write("/");
sw.write(skin);
sw.write("/tool.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n"
+ "<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />\n" + "<title>");
sw.write(title);
sw.write("</title></head><body><div class=\"portletBody\">\n" + "<h2>");
sw.write(title);
sw.write("</h2><ul><li>Date ");
sw.write(date);
sw.write("</li>");
sw.write("<li>From ");
sw.write(from);
sw.write("</li>");
sw.write(groups);
sw.write("<ul><p>");
sw.write(body);
sw.write("</p></div></body></html> ");
sw.flush();
res.setContentType("text/html");
res.setContentLength(outByteStream.size());
if (outByteStream.size() > 0)
{
// Increase the buffer size for more speed.
res.setBufferSize(outByteStream.size());
}
OutputStream out = null;
try
{
out = res.getOutputStream();
if (outByteStream.size() > 0)
{
outByteStream.writeTo(out);
}
out.flush();
out.close();
}
catch (Throwable ignore)
{
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (Throwable ignore)
{
}
}
}
}
catch (PermissionException e)
{
throw new EntityPermissionException(e.getUser(), e.getLocalizedMessage(), e.getResource());
}
catch (IdUnusedException e)
{
throw new EntityNotDefinedException(e.getId());
}
catch (Throwable t)
{
throw new RuntimeException("Faied to find message ", t);
}
}
};
}
/**
* {@inheritDoc}
*/
public String getLabel() {
return getChatManager().getLabel();
}
/**
* {@inheritDoc}
*/
public String merge(String siteId, Element root, String archivePath, String fromSiteId, Map attachmentNames, Map userIdTrans,
Set userListAllowImport)
{
logger.debug("trying to merge chat");
// buffer for the results log
StringBuilder results = new StringBuilder();
int count = 0;
if (siteId != null && siteId.trim().length() > 0)
{
try
{
NodeList allChildrenNodes = root.getChildNodes();
int length = allChildrenNodes.getLength();
for (int i = 0; i < length; i++)
{
count++;
Node siteNode = allChildrenNodes.item(i);
if (siteNode.getNodeType() == Node.ELEMENT_NODE)
{
Element chatElement = (Element) siteNode;
if (chatElement.getTagName().equals(ChatManager.CHAT))
{
Site site = SiteService.getSite(siteId);
if (site.getToolForCommonId(ChatManager.CHAT_TOOL_ID) != null) {
// add the chat rooms and synoptic tool options
NodeList chatNodes = chatElement.getChildNodes();
int lengthChatNodes = chatNodes.getLength();
for (int cn = 0; cn < lengthChatNodes; cn++)
{
Node chatNode = chatNodes.item(cn);
if (chatNode.getNodeType() == Node.ELEMENT_NODE)
{
Element channelElement = (Element) chatNode;
if (channelElement.getTagName().equals(CHANNEL_PROP)) {
ChatChannel channel = ChatChannel.xmlToChatChannel(channelElement, siteId);
//save the channel
getChatManager().updateChannel(channel, false);
}
else if (channelElement.getTagName().equals(SYNOPTIC_TOOL))
{
ToolConfiguration synTool = site.getToolForCommonId("sakai.synoptic.chat");
Properties synProps = synTool.getPlacementConfig();
NodeList synPropNodes = channelElement.getChildNodes();
for (int props = 0; props < synPropNodes.getLength(); props++)
{
Node propsNode = synPropNodes.item(props);
if (propsNode.getNodeType() == Node.ELEMENT_NODE)
{
Element synPropEl = (Element) propsNode;
if (synPropEl.getTagName().equals(PROPERTIES))
{
NodeList synProperties = synPropEl.getChildNodes();
for (int p = 0; p < synProperties.getLength(); p++)
{
Node propertyNode = synProperties.item(p);
if (propertyNode.getNodeType() == Node.ELEMENT_NODE)
{
Element propEl = (Element) propertyNode;
if (propEl.getTagName().equals(PROPERTY))
{
String propName = propEl.getAttribute(NAME);
String propValue = propEl.getAttribute(VALUE);
if (propName != null && propName.length() > 0 && propValue != null && propValue.length() > 0)
{
synProps.setProperty(propName, propValue);
}
}
}
}
}
}
}
}
}
}
SiteService.save(site);
}
}
}
}
results.append("merging chat " + siteId + " (" + count
+ ") chat items.\n");
}
catch (DOMException e)
{
logger.error(e.getMessage(), e);
results.append("merging " + getLabel()
+ " failed during xml parsing.\n");
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
results.append("merging " + getLabel() + " failed.\n");
}
}
return results.toString();
} // merge
/**
* {@inheritDoc}
*/
public boolean parseEntityReference(String reference, Reference ref)
{
if (reference.startsWith(ChatManager.REFERENCE_ROOT))
{
String[] parts = StringUtil.split(reference, Entity.SEPARATOR);
String id = null;
String subType = null;
String context = null;
String container = null;
// the first part will be null, then next the service, the third will be "msg" or "channel"
if (parts.length > 2)
{
subType = parts[2];
if (ChatManager.REF_TYPE_CHANNEL.equals(subType))
{
// next is the context id
if (parts.length > 3)
{
context = parts[3];
// next is the channel id
if (parts.length > 4)
{
id = parts[4];
}
}
}
else if (ChatManager.REF_TYPE_MESSAGE.equals(subType))
{
// next three parts are context, channel (container) and mesage id
if (parts.length > 5)
{
context = parts[3];
container = parts[4];
id = parts[5];
}
}
else
logger.warn("parse(): unknown message subtype: " + subType + " in ref: " + reference);
}
ref.set(ChatManager.APPLICATION_ID, subType, id, container, context);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean willArchiveMerge()
{
return true;
}
/**
* {@inheritDoc}
*
* TODO: link the old placement id to the new placement id instead of passing null in line:
* ChatChannel newChannel = getChatManager().createNewChannel(toContext, oldChannel.getTitle(), false, false, null);
*/
public void transferCopyEntities(String fromContext, String toContext, List ids)
{
try
{
// retrieve all of the chat rooms
List channels = getChatManager().getContextChannels(fromContext, true);
if (channels != null && !channels.isEmpty())
{
Iterator channelIterator = channels.iterator();
while (channelIterator.hasNext())
{
ChatChannel oldChannel = (ChatChannel)channelIterator.next();
ChatChannel newChannel = getChatManager().createNewChannel(toContext, oldChannel.getTitle(), false, false, null);
newChannel.setDescription(oldChannel.getDescription());
newChannel.setFilterType(oldChannel.getFilterType());
newChannel.setFilterParam(oldChannel.getFilterParam());
newChannel.setPlacementDefaultChannel(oldChannel.isPlacementDefaultChannel());
try {
getChatManager().updateChannel(newChannel, false);
}
catch (Exception e)
{
logger.warn("Exception while creating channel: " + newChannel.getTitle() + ": " + e);
}
}
}
transferSynopticOptions(fromContext, toContext);
}
catch (Exception any)
{
logger.warn(".transferCopyEntities(): exception in handling " + serviceName() + " : ", any);
}
}
/**
* Import the synoptic tool options from another site
*
* @param fromContext
* @param toContext
*/
protected void transferSynopticOptions(String fromContext, String toContext)
{
try
{
// transfer the synoptic tool options
Site fromSite = SiteService.getSite(fromContext);
ToolConfiguration fromSynTool = fromSite.getToolForCommonId("sakai.synoptic." + getLabel());
Properties fromSynProp = fromSynTool.getPlacementConfig();
Site toSite = SiteService.getSite(toContext);
ToolConfiguration toSynTool = toSite.getToolForCommonId("sakai.synoptic." + getLabel());
Properties toSynProp = toSynTool.getPlacementConfig();
if (fromSynProp != null && !fromSynProp.isEmpty())
{
Set synPropSet = fromSynProp.keySet();
Iterator propIter = synPropSet.iterator();
while (propIter.hasNext())
{
String propName = ((String)propIter.next());
String propValue = fromSynProp.getProperty(propName);
if (propValue != null && propValue.length() > 0)
{
toSynProp.setProperty(propName, propValue);
}
}
SiteService.save(toSite);
}
}
catch (PermissionException pe)
{
logger.warn("PermissionException transferring synoptic options for " + serviceName() + ':', pe);
}
catch (IdUnusedException e)
{
logger.warn("Channel " + fromContext + " cannot be found. ");
}
catch (Exception e)
{
logger.warn("transferSynopticOptions(): exception in handling " + serviceName() + " : ", e);
}
}
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public ChatManager getChatManager() {
return chatManager;
}
public void setChatManager(ChatManager chatManager) {
this.chatManager = chatManager;
}
public void transferCopyEntities(String fromContext, String toContext, List ids, boolean cleanup)
{
try
{
if(cleanup == true)
{
// retrieve all of the chat rooms
List channels = getChatManager().getContextChannels(toContext, true);
if (channels != null && !channels.isEmpty())
{
Iterator channelIterator = channels.iterator();
while (channelIterator.hasNext())
{
ChatChannel oldChannel = (ChatChannel)channelIterator.next();
try
{
getChatManager().deleteChannel(oldChannel);
}
catch (Exception e)
{
logger.debug("Exception while removing chat channel: " + e);
}
}
}
}
transferCopyEntities(fromContext, toContext, ids);
}
catch (Exception e)
{
logger.debug("Chat transferCopyEntities(): exception in handling " + e);
}
}
}