/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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.apache.org/licenses/LICENSE-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.apache.muse.ws.notification.topics.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.muse.util.StringUtils;
import org.apache.muse.util.messages.Messages;
import org.apache.muse.util.messages.MessagesFactory;
import org.apache.muse.util.xml.XPathUtils;
import org.apache.muse.util.xml.XmlUtils;
import org.apache.muse.util.xml.XsdUtils;
import org.apache.muse.ws.resource.basefaults.BaseFault;
import org.apache.muse.ws.notification.NotificationMessage;
import org.apache.muse.ws.notification.faults.InvalidTopicExpressionFault;
import org.apache.muse.ws.notification.topics.Topic;
import org.apache.muse.ws.notification.topics.TopicPathExpression;
import org.apache.muse.ws.notification.topics.TopicNamespace;
import org.apache.muse.ws.notification.topics.WstConstants;
/**
*
* SimpleTopic is Muse's default implementation of the wsnt:Topic data structure
* defined in WS-Notification v1.3 and WS-Topics v1.3.
*
* @author Dan Jemiolo (danj)
*
*/
public class SimpleTopic implements Topic
{
private static Messages _MESSAGES = MessagesFactory.get(SimpleTopic.class);
//
// keep children in the order they were added for serialization purposes
//
private Map _childTopics = new LinkedHashMap();
//
// the last message published for this topic
//
private NotificationMessage _currentMessage = null;
private boolean _isFinal = false;
private String _messagePattern = null;
private Set _messageTypes = new HashSet();
private String _name = null;
private Topic _parentTopic = null;
private TopicNamespace _topicSpace = null;
public SimpleTopic(Element root, TopicNamespace topicSpace)
throws InvalidTopicExpressionFault, BaseFault
{
if (root == null)
throw new NullPointerException(_MESSAGES.get("NullTopicElement"));
if (topicSpace == null)
throw new NullPointerException(_MESSAGES.get("NullTopicSpace"));
_topicSpace = topicSpace;
_name = root.getAttribute(XsdUtils.NAME);
if (_name == null || _name.length() == 0)
throw new InvalidTopicExpressionFault(_MESSAGES.get("NoTopicName"));
String finalValue = root.getAttribute(WstConstants.FINAL);
if (finalValue != null && finalValue.length() > 0)
_isFinal = Boolean.valueOf(finalValue).booleanValue();
String messageTypesValue = root.getAttribute(WstConstants.MESSAGE_TYPES);
if (messageTypesValue != null && messageTypesValue.length() > 0)
{
String[] values = StringUtils.split(messageTypesValue);
for (int n = 0; n < values.length; ++n)
{
QName qname = XmlUtils.parseQName(values[n], root);
_messageTypes.add(qname);
}
}
_messagePattern = XmlUtils.getElementText(root, WstConstants.PATTERN_QNAME);
Element[] children = XmlUtils.getElements(root, WstConstants.TOPIC_QNAME);
for (int n = 0; n < children.length; ++n)
{
SimpleTopic topic = new SimpleTopic(children[n], topicSpace);
addTopicEvenIfFinal(topic);
}
}
public SimpleTopic(String name, TopicNamespace topicSpace)
throws BaseFault
{
if (name == null)
throw new NullPointerException(_MESSAGES.get("NullTopicName"));
if (topicSpace == null)
throw new NullPointerException(_MESSAGES.get("NullTopicSpace"));
_topicSpace = topicSpace;
int slash = name.indexOf('/');
int end = slash >= 0 ? slash : name.length();
_name = name.substring(0, end);
if (end < name.length())
{
String childName = name.substring(end + 1);
addTopic(new SimpleTopic(childName, topicSpace));
}
}
public synchronized final void addMessageType(QName messageType)
{
if (messageType == null)
throw new NullPointerException(_MESSAGES.get("NullMessageType"));
_messageTypes.add(messageType);
}
public synchronized final void addTopic(Topic childTopic)
throws InvalidTopicExpressionFault, BaseFault
{
if (childTopic == null)
throw new NullPointerException(_MESSAGES.get("NullTopic"));
if (isFinal())
{
Object[] filler = { getConcretePath() };
throw new InvalidTopicExpressionFault(_MESSAGES.get("NoNewTopics", filler));
}
addTopicEvenIfFinal(childTopic);
}
private void addTopicEvenIfFinal(Topic childTopic)
throws InvalidTopicExpressionFault, BaseFault
{
String name = childTopic.getName();
if (_childTopics.containsKey(name))
{
Object[] filler = { name, getConcretePath() };
throw new InvalidTopicExpressionFault(_MESSAGES.get("SubTopicExists", filler));
}
String topicNS = childTopic.getTopicNamespace().getTargetNamespace();
String targetNS = getTopicNamespace().getTargetNamespace();
if (!targetNS.equals(topicNS))
{
Object[] filler = { topicNS, targetNS };
throw new InvalidTopicExpressionFault(_MESSAGES.get("InvalidTopicNS", filler));
}
childTopic.setParentTopic(this);
_childTopics.put(name, childTopic);
}
/**
*
* @return True if the two topics have the same local name, topic namespace,
* concrete path, message pattern, and 'final' status.
*
*/
public synchronized boolean equals(Object obj)
{
if (obj == null)
return false;
Topic that = (Topic)obj;
if (!getName().equals(that.getName()))
return false;
String thisNS = getTopicNamespace().getTargetNamespace();
String thatNS = that.getTopicNamespace().getTargetNamespace();
if (!thisNS.equals(thatNS))
return false;
if (!getConcretePath().equals(that.getConcretePath()))
return false;
if (isFinal() != that.isFinal())
return false;
String thisPattern = getMessagePattern();
String thatPattern = that.getMessagePattern();
if (thisPattern != null)
return thisPattern.equals(thatPattern);
return thatPattern == null;
}
public synchronized QName getConcretePath()
{
TopicPathExpression path = new ConcretePathExpression(this);
return path.getTopicPath();
}
public synchronized NotificationMessage getCurrentMessage()
{
return _currentMessage;
}
/**
*
* @return An XPath that further expresses the types of messages
* this topic publishes.
*
*/
public synchronized String getMessagePattern()
{
return _messagePattern;
}
/**
*
* @return The XPath 1.0 namespace.
*
*/
public String getMessagePatternDialect()
{
return XPathUtils.NAMESPACE_URI;
}
public Set getMessageTypes()
{
return Collections.unmodifiableSet(_messageTypes);
}
public final String getName()
{
return _name;
}
public synchronized Topic getParentTopic()
{
return _parentTopic;
}
public synchronized Topic getTopic(String topicName)
{
return (Topic)_childTopics.get(topicName);
}
public synchronized TopicNamespace getTopicNamespace()
{
return _topicSpace;
}
public synchronized Collection getTopics()
{
return Collections.unmodifiableCollection(_childTopics.values());
}
/**
*
* @return A hash code based on the same values used for testing equality.
*
* @see #equals(Object)
*
*/
public synchronized int hashCode()
{
String pattern = getMessagePattern();
String ns = getTopicNamespace().getTargetNamespace();
return getName().hashCode() +
ns.hashCode() +
getConcretePath().hashCode() +
( isFinal() ? 1 : 0 ) +
( pattern == null ? 0 : pattern.hashCode());
}
public synchronized boolean hasTopic(String topicName)
{
return getTopic(topicName) != null;
}
public synchronized boolean isFinal()
{
return _isFinal;
}
public synchronized boolean isRootTopic()
{
return getParentTopic() == null;
}
public synchronized final void removeAllTopics()
{
_childTopics.clear();
}
public synchronized final void removeMessageType(QName messageType)
{
if (messageType == null)
throw new NullPointerException(_MESSAGES.get("NullMessageType"));
_messageTypes.remove(messageType);
}
public synchronized final void removeTopic(String topicName)
{
if (topicName == null)
throw new NullPointerException(_MESSAGES.get("NullTopicName"));
_childTopics.remove(topicName);
}
public void setCurrentMessage(NotificationMessage message)
{
_currentMessage = message;
}
public synchronized void setFinal(boolean isFinal)
{
_isFinal = isFinal;
}
public synchronized void setMessagePattern(String messagePattern)
{
_messagePattern = messagePattern;
}
public synchronized void setParentTopic(Topic parentTopic)
{
_parentTopic = parentTopic;
}
public synchronized String toString()
{
return XmlUtils.toString(toXML(), false);
}
public synchronized Element toXML()
{
return toXML(XmlUtils.EMPTY_DOC);
}
public synchronized Element toXML(Document doc)
{
if (doc == null)
throw new NullPointerException(_MESSAGES.get("NullDocument"));
Element root = XmlUtils.createElement(doc, WstConstants.TOPIC_QNAME);
root.setAttribute(XsdUtils.NAME, getName());
root.setAttribute(XmlUtils.TARGET_NS, getTopicNamespace().getTargetNamespace());
root.setAttribute(WstConstants.FINAL, Boolean.toString(isFinal()));
String pattern = getMessagePattern();
if (pattern != null)
{
Element patternXML =
XmlUtils.createElement(doc, WstConstants.PATTERN_QNAME, pattern);
root.appendChild(patternXML);
patternXML.setAttribute(WstConstants.DIALECT, XPathUtils.NAMESPACE_URI);
}
Set messageTypes = getMessageTypes();
if (!messageTypes.isEmpty())
{
Iterator i = messageTypes.iterator();
int size = messageTypes.size();
StringBuffer buffer = new StringBuffer(size * 128);
for (int n = 1; i.hasNext(); ++n)
{
QName messageName = (QName)i.next();
buffer.append(XmlUtils.toString(messageName));
if (n != size)
buffer.append(' ');
}
root.setAttribute(WstConstants.MESSAGE_TYPES, buffer.toString());
}
Iterator i = getTopics().iterator();
while (i.hasNext())
{
Topic topic = (Topic)i.next();
Element topicXML = topic.toXML(doc);
root.appendChild(topicXML);
}
return root;
}
}