/*------------------------------------------------------------------------------
Name: ConnectQosSaxFactory.java
Project: xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
Comment: Parsing connect QoS
------------------------------------------------------------------------------*/
package org.xmlBlaster.util.qos;
import java.util.Properties;
import java.util.logging.Logger;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.qos.address.Address;
import org.xmlBlaster.util.qos.address.AddressBase;
import org.xmlBlaster.util.qos.address.CallbackAddress;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.qos.storage.ClientQueueProperty;
import org.xmlBlaster.util.qos.storage.CbQueueProperty;
import org.xmlBlaster.util.qos.address.ServerRef;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.authentication.plugins.I_SecurityQos;
import org.xmlBlaster.util.SessionName;
import org.xml.sax.Attributes;
/**
* This class encapsulates the qos of a login() or connect().
* <p />
* So you don't need to type the 'ugly' XML ASCII string by yourself.
* After construction access the ASCII-XML string with the toXml() method.
* <br />
* A typical <b>connect QoS</b> could look like this:<br />
* <pre>
*<qos>
* <securityService type="htpasswd" version="1.0">
* <![CDATA[
* <user>joe</user>
* <passwd>secret</passwd>
* ]]>
* </securityService>
*
* <session name='/node/heron/client/joe' timeout='3600000'
maxSessions='10' clearSessions='false'
reconnectSameClientOnly='false'/>
*
* <ptp>true</ptp> <!-- Allow receiving PtP messages (no SPAM protection) -->
*
* <duplicateUpdates>true</duplicateUpdates>
*
* <!-- The client side queue (is ignored on server side): -->
* <queue relating='client' type='CACHE' version='1.0' maxEntries='1000' maxBytes='4000' onOverflow='exception'>
* <address type='IOR'>
* IOR:10000010033200000099000010....
* </address>
* <queue>
*
* <!-- Configures the server side callback queue: -->
* <queue relating='callback' type='CACHE' version='1.0' maxEntries='1000' maxBytes='4000' onOverflow='deadMessage'>
* <callback type='IOR' sessionId='4e56890ghdFzj0'>
* IOR:10000010033200000099000010....
* <burstMode collectTime='400' />
* </callback>
* </queue>
*</qos>
* </pre>
* NOTE: As a user of the Java client helper classes (client.I_XmlBlasterAccess)
* you don't need to create the <pre><callback></pre> element.
* This is generated automatically from I_XmlBlasterAccess when instantiating
* the callback driver.
*
* <p />
*
* A typical <b>connect return QoS</b> could look like this (this is the acknowledge returned by
* the server to the client on successful connect):<br />
* <pre>
*<qos>
* <securityService type="htpasswd" version="1.0">
* <![CDATA[
* <user>joe</user>
* <passwd>secret</passwd>
* ]]>
* </securityService>
*
* <session name='/node/heron/client/joe/-9' timeout='3600000' maxSessions='10' clearSessions='false'
* clearSessions='false' sessionId='sessionId:192.168.1.4-null-1042823803521-2074317763-3'/>
*
* <reconnected>false</reconnected> <!-- Has the client reconnected to an existing session? -->
*
* <!-- The server side callback queue: -->
* <queue relating='callback' type='CACHE' version='1.0' maxEntries='1000' maxBytes='4000' onOverflow='deadMessage'>
* <callback type='XMLRPC' bootstrapHostname='192.168.1.4' sessionId='4e56890ghdFzj0'>
* http://192.168.1.4:8081/
* <burstMode collectTime='400' />
* </callback>
* <queue>
*</qos>
* </pre>
* @see org.xmlBlaster.test.classtest.ConnectQosTest
* @see <a href="http://www.xmlblaster.org/xmlBlaster/doc/requirements/interface.connect.html">connect interface</a>
*/
public final class ConnectQosSaxFactory extends org.xmlBlaster.util.XmlQoSBase implements I_ConnectQosFactory
{
private final Global glob;
private static Logger log = Logger.getLogger(ConnectQosSaxFactory.class.getName());
private ConnectQosData connectQosData;
// helper flags for SAX parsing
//private boolean inServerRef;
private boolean inQueue;
private boolean inSecurityService;
//private boolean inSession;
private boolean inCallback;
private boolean inAddress;
/** Helper for SAX parsing */
private ServerRef tmpServerRef;
private CbQueueProperty tmpCbProp;
private CallbackAddress tmpCbAddr;
private ClientQueueProperty tmpProp;
private Address tmpAddr;
protected String tmpSecurityPluginType;
protected String tmpSecurityPluginVersion;
//private boolean inIsPersistent = false;
/**
*/
public ConnectQosSaxFactory(Global glob) {
super(glob);
this.glob = glob;
}
/**
* Parses the given xml Qos and returns a ConnectQosData holding the data.
* Parsing of connect() and connect-return QoS is supported here.
* This call is thread safe if not intermixed with <tt>getConnectQosData()</tt> calls.
* @param the XML based ASCII string
*/
public synchronized ConnectQosData readObject(String xmlQos) throws XmlBlasterException {
if (xmlQos == null) {
xmlQos = "<qos/>";
}
this.inQueue = false;
this.inSecurityService = false;
//this.inSession = false;
this.inCallback = false;
this.inAddress = false;
this.tmpServerRef = null;
this.tmpCbProp = null;
this.tmpCbAddr = null;
this.tmpProp = null;
this.tmpAddr = null;
this.tmpSecurityPluginType = null;
this.tmpSecurityPluginVersion = null;
//this.inIsPersistent = false;
this.connectQosData = new ConnectQosData(glob, this, xmlQos, null);
if (!isEmpty(xmlQos)) // if possible avoid expensive SAX parsing
init(xmlQos); // use SAX parser to parse it (is slow)
return this.connectQosData;
}
/**
* This is NOT thread safe so you need a new factory for each parse.
* Use this variant if you have a bigger xml markup and want to delegate
* startElement(), endElement() calls to this.
* PRECONDITION: Call setConnectQosData() first!
*/
public ConnectQosData getConnectQosData() {
return this.connectQosData;
}
/** If a delegate call startElement() directly */
public void setConnectQosData(ConnectQosData data) {
this.connectQosData = data;
}
/**
* Start element, event from SAX parser.
* <p />
* @param name Tag name
* @param attrs the attributes of the tag
*/
public void startElement(String uri, String localName, String name, Attributes attrs) {
startElement(uri, localName, name, this.character, attrs);
}
/**
* Start element from SAX parsing, call as delegate delivers the character
*/
public void startElement(String uri, String localName, String name, StringBuffer character, Attributes attrs) {
if (this.clientPropertyTagNames.contains(name) && character.toString().trim().length() > 0) { // If e.g. email address is written before <attribute> tag
if (inCallback)
tmpCbAddr.setRawAddress(character.toString().trim());
if (inAddress)
tmpAddr.setRawAddress(character.toString().trim());
}
if (super.startElementBase(uri, localName, name, attrs) == true)
return;
//if (log.isLoggable(Level.FINE)) log.trace(ME, "Entering startElement for uri=" + uri + " localName=" + localName + " name=" + name);
if (name.equalsIgnoreCase("serverRef")) {
//this.inServerRef = true;
String tmp = character.toString().trim(); // The address (if before inner tags)
String type = null;
if (attrs != null) {
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
if( attrs.getQName(i).equalsIgnoreCase("type") ) {
type = attrs.getValue(i).trim();
break;
}
}
}
if (type == null) {
log.severe("Missing 'serverRef' attribute 'type' in login-qos");
type = AddressBase.DEFAULT_type;// Since 1.0.7 "SOCKET", before "IOR";
}
tmpServerRef = new ServerRef(type);
if (tmp.length() > 0) {
tmpServerRef.setAddress(tmp);
character.setLength(0);
}
return;
}
if (inCallback) {
tmpCbAddr.startElement(uri, localName, name, character, attrs);
return;
}
if (name.equalsIgnoreCase("callback")) {
inCallback = true;
if (!inQueue) {
tmpCbProp = new CbQueueProperty(glob, Constants.RELATING_CALLBACK, null); // Use default queue properties for this callback address
this.connectQosData.setSessionCbQueueProperty(tmpCbProp);
}
tmpCbAddr = new CallbackAddress(glob);
tmpCbAddr.startElement(uri, localName, name, character, attrs);
tmpCbProp.setCallbackAddress(tmpCbAddr);
return;
}
if (name.equalsIgnoreCase("address")) {
inAddress = true;
boolean accepted=true;
if (!inQueue) {
tmpProp = new ClientQueueProperty(glob, null); // Use default queue properties for this connection address
accepted = this.connectQosData.addClientQueueProperty(tmpProp);
}
tmpAddr = new Address(glob);
tmpAddr.startElement(uri, localName, name, character, attrs);
if (accepted) {
tmpProp.setAddress(tmpAddr);
}
return;
}
if (name.equalsIgnoreCase("queue")) {
inQueue = true;
if (inCallback) {
log.severe("<queue> tag is not allowed inside <callback> tag, element ignored.");
character.setLength(0);
return;
}
if (inAddress) {
log.severe("<queue> tag is not allowed inside <address> tag, element ignored.");
character.setLength(0);
return;
}
String related = attrs.getValue("relating");
if (Constants.RELATING_CLIENT.equalsIgnoreCase(related)) {
tmpProp = new ClientQueueProperty(glob, null);
tmpProp.startElement(uri, localName, name, attrs);
/*boolean accepted = */this.connectQosData.addClientQueueProperty(tmpProp);
}
else if (Constants.RELATING_CALLBACK.equalsIgnoreCase(related)) {
tmpCbProp = new CbQueueProperty(glob, Constants.RELATING_CALLBACK, null);
tmpCbProp.startElement(uri, localName, name, attrs);
this.connectQosData.setSessionCbQueueProperty(tmpCbProp);
}
else if (Constants.RELATING_SUBJECT.equalsIgnoreCase(related)) {
tmpCbProp = new CbQueueProperty(glob, Constants.RELATING_SUBJECT, null);
tmpCbProp.startElement(uri, localName, name, attrs);
this.connectQosData.setSubjectQueueProperty(tmpCbProp);
}
else {
log.warning("The given relating='" + related + "' is not supported, configuration for '" + related + "' is ignored");
}
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("securityService")) {
inSecurityService = true;
boolean existsTypeAttr = false;
boolean existsVersionAttr = false;
if (attrs != null) {
int len = attrs.getLength();
int ii=0;
for (ii = 0; ii < len; ii++) {
if (attrs.getQName(ii).equalsIgnoreCase("type")) {
existsTypeAttr = true;
tmpSecurityPluginType = attrs.getValue(ii).trim();
}
else if (attrs.getQName(ii).equalsIgnoreCase("version")) {
existsVersionAttr = true;
tmpSecurityPluginVersion = attrs.getValue(ii).trim();
}
}
}
if (!existsTypeAttr) log.severe("Missing 'type' attribute in login-qos <securityService>");
if (!existsVersionAttr) log.severe("Missing 'version' attribute in login-qos <securityService>");
character.setLength(0);
// Fall through and collect xml, will be parsed later by appropriate security plugin
}
if (name.equalsIgnoreCase("session")) {
//this.inSession = true;
if (attrs != null) {
int len = attrs.getLength();
int ii=0;
SessionQos sessionQos = this.connectQosData.getSessionQos();
for (ii = 0; ii < len; ii++) {
if (attrs.getQName(ii).equalsIgnoreCase("name")) {
if (glob.isServerSide()) { // Force the server node ID on connect
sessionQos.setSessionName(new SessionName(glob, glob.getNodeId(), attrs.getValue(ii).trim()));
}
else {
sessionQos.setSessionName(new SessionName(glob, attrs.getValue(ii).trim()));
}
}
else if (attrs.getQName(ii).equalsIgnoreCase("timeout"))
sessionQos.setSessionTimeout((new Long(attrs.getValue(ii).trim())).longValue());
else if (attrs.getQName(ii).equalsIgnoreCase("maxSessions"))
sessionQos.setMaxSessions((new Integer(attrs.getValue(ii).trim())).intValue());
else if (attrs.getQName(ii).equalsIgnoreCase("clearSessions"))
sessionQos.clearSessions((new Boolean(attrs.getValue(ii).trim())).booleanValue());
else if (attrs.getQName(ii).equalsIgnoreCase("reconnectSameClientOnly"))
sessionQos.setReconnectSameClientOnly((new Boolean(attrs.getValue(ii).trim())).booleanValue());
else if (attrs.getQName(ii).equalsIgnoreCase("sessionId"))
sessionQos.setSecretSessionId(attrs.getValue(ii));
else
log.warning("Ignoring unknown attribute '" + attrs.getQName(ii) + "' of <session> element");
}
}
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("ptp")) {
this.connectQosData.setPtpAllowed(true);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("clusterNode")) {
this.connectQosData.setClusterNode(true);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("refreshSession")) {
this.connectQosData.setRefreshSession(true);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("duplicateUpdates")) {
this.connectQosData.setDuplicateUpdates(true);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("reconnected")) {
this.connectQosData.setReconnected(true);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("persistent")) {
if (!inQos)
return;
//this.inIsPersistent = true;
character.setLength(0);
connectQosData.setPersistent(true);
return;
}
if (inSecurityService) {
//Collect everything in character buffer
character.append("<").append(name);
if (attrs != null) {
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
character.append(" ").append(attrs.getQName(i)).append("=\"").append(attrs.getValue(i)).append("\"");
}
}
character.append(">");
if (name.equalsIgnoreCase("securityService"))
character.append("<![CDATA[");
}
}
/**
* End element, event from SAX parser.
* <p />
* @param name Tag name
*/
public void endElement(String uri, String localName, String name) throws org.xml.sax.SAXException {
endElement(uri, localName, name, this.character);
}
public void endElement(String uri, String localName, String name, StringBuffer character) throws org.xml.sax.SAXException {
if (super.endElementBase(uri, localName, name) == true) {
// "clientProperty" and "attribute" must match super.clientPropertyTagNames.contains(name))
if (name.equalsIgnoreCase(ClientProperty.CLIENTPROPERTY_TAG)) {
this.connectQosData.addClientProperty(this.clientProperty);
}
if (name.equalsIgnoreCase(ClientProperty.ATTRIBUTE_TAG)) {
if (this.inAddress)
this.tmpAddr.addClientProperty(this.clientProperty);
else if (this.inCallback)
this.tmpCbAddr.addClientProperty(this.clientProperty);
}
return;
}
//if (log.isLoggable(Level.FINE)) log.trace(ME, "Entering endElement for " + name);
if (name.equalsIgnoreCase("serverRef")) {
//this.inServerRef = false;
String tmp = character.toString().trim(); // The address (if after inner tags)
if (tmpServerRef != null) {
if (tmp.length() > 0) tmpServerRef.setAddress(tmp);
this.connectQosData.addServerRef(tmpServerRef);
}
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("queue")) {
inQueue = false;
character.setLength(0);
return;
}
if (inCallback) {
if (name.equalsIgnoreCase("callback")) inCallback = false;
tmpCbAddr.endElement(uri, localName, name, character);
character.setLength(0);
return;
}
if (inAddress) {
if (name.equalsIgnoreCase("address")) inAddress = false;
tmpAddr.endElement(uri, localName, name, character);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("ptp")) {
String tmp = character.toString().trim();
if (tmp.length() > 0)
this.connectQosData.setPtpAllowed(new Boolean(tmp).booleanValue());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("clusterNode")) {
String tmp = character.toString().trim();
if (tmp.length() > 0)
this.connectQosData.setClusterNode(new Boolean(tmp).booleanValue());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("refreshSession")) {
String tmp = character.toString().trim();
if (tmp.length() > 0)
this.connectQosData.setRefreshSession(new Boolean(tmp).booleanValue());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("duplicateUpdates")) {
String tmp = character.toString().trim();
if (tmp.length() > 0)
this.connectQosData.setDuplicateUpdates(new Boolean(tmp).booleanValue());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("reconnected")) {
String tmp = character.toString().trim();
if (tmp.length() > 0)
this.connectQosData.setReconnected(new Boolean(tmp).booleanValue());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("instanceId")) {
String tmp = character.toString().trim();
this.connectQosData.setInstanceId(tmp);
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("securityService")) {
inSecurityService = false;
character.append("]]>\n");
character.append("</").append(name).append(">");
String tmp = character.toString().trim();
character.setLength(0);
// delegate the collected tags to our security plugin
try {
I_SecurityQos securityQos = this.connectQosData.getClientPlugin(tmpSecurityPluginType, tmpSecurityPluginVersion).createSecurityQos();
/*
"<securityService type=\""+tmpSecurityPluginType+"\" version=\""+tmpSecurityPluginVersion+"\">\n"+
<user>user</user>
<passwd>passwd</passwd>
"</securityService>";
*/
securityQos.parse(tmp);
this.connectQosData.setSecurityQos(securityQos);
return;
}
catch(XmlBlasterException e) {
log.warning("Can't parse security string - " + e.toString() + "\n Check:\n" + tmp);
throw new org.xml.sax.SAXException("Can't parse security string - " + e.toString() + "\n Check:\n" + tmp, e);
//throw new StopParseException();
}
}
if(name.equalsIgnoreCase("persistent")) {
//this.inIsPersistent = false;
String tmp = character.toString().trim();
if (tmp.length() > 0)
connectQosData.setPersistent(new Boolean(tmp).booleanValue());
// if (log.isLoggable(Level.FINE)) log.trace(ME, "Found persistent = " + msgQosData.isPersistent());
character.setLength(0);
return;
}
if (name.equalsIgnoreCase("session")) {
//this.inSession = false;
}
if (inSecurityService) {
character.append("</"+name+">");
}
}
/**
* Dump state of this object into a XML ASCII string.
* <br>
* @param extraOffset indenting of tags for nice output
* @return internal state of the ConnectQos as a XML ASCII string
*//*
public String writeObject(ConnectQosData connectQosData, String extraOffset) {
return toXml("qos", connectQosData, extraOffset, (Properties)null);
}*/
public String writeObject(ConnectQosData connectQosData, String extraOffset, Properties props) {
return toXml("qos", connectQosData, extraOffset, props);
}
/**
* @param flag For example Constants.TOXML_FLAG_NOSECURITY
*/
public static final String toXml(String rootTag, ConnectQosData data, String extraOffset, Properties props) {
StringBuffer sb = new StringBuffer(2000);
if (extraOffset == null) extraOffset = "";
String offset = Constants.OFFSET + extraOffset;
sb.append(offset).append("<").append(rootTag).append(">");
if (data.getSecurityQos() != null) { // <securityService ...
final boolean noSecurity = ((props!=null) && props.containsKey(Constants.TOXML_NOSECURITY)) ?
(Boolean.valueOf((String)props.get(Constants.TOXML_NOSECURITY)).booleanValue()) :
false;
if (!noSecurity) {
sb.append(data.getSecurityQos().toXml(extraOffset+Constants.INDENT)); // includes the qos of the ClientSecurityHelper
}
}
if (data.isPtpAllowedProp().isModified()) {
if (data.isPtpAllowed())
sb.append(offset).append(" <ptp/>");
else
sb.append(offset).append(" <ptp>false</ptp>");
}
if (data.getClusterNodeProp().isModified()) {
if (data.isClusterNode())
sb.append(offset).append(" <clusterNode/>");
else
sb.append(offset).append(" <clusterNode>false</clusterNode>");
}
if (data.getRefreshSessionProp().isModified()) {
if (data.getRefreshSession())
sb.append(offset).append(" <refreshSession/>");
else
sb.append(offset).append(" <refreshSession>false</refreshSession>");
}
if (data.duplicateUpdatesProp().isModified()) {
if (data.duplicateUpdates())
sb.append(offset).append(" <duplicateUpdates/>");
else
sb.append(offset).append(" <duplicateUpdates>false</duplicateUpdates>");
}
if (data.getReconnectedProp().isModified()) {
if (data.isReconnected())
sb.append(offset).append(" <reconnected/>");
else
sb.append(offset).append(" <reconnected>false</reconnected>");
}
sb.append(offset).append(" <instanceId>").append(data.getInstanceId()).append("</instanceId>");
if (data.getPersistentProp().isModified()) {
if (data.isPersistent())
sb.append(offset).append(" <persistent/>");
else
sb.append(offset).append(" <persistent>false</persistent>");
}
sb.append(data.getSessionQos().toXml(extraOffset+Constants.INDENT));
{
ClientQueueProperty[] arr = data.getClientQueuePropertyArr();
for (int ii=0; arr!=null && ii<arr.length; ii++) {
sb.append(arr[ii].toXml(extraOffset+Constants.INDENT));
}
}
/*
NOTE: We can't optimize it away, as we need to check the Global env and args:
*/
//if (data.hasSubjectQueueProperty()) {
sb.append(data.getSubjectQueueProperty().toXml(extraOffset+Constants.INDENT));
//}
//if (data.hasSessionCbQueueProperty()) {
sb.append(data.getSessionCbQueueProperty().toXml(extraOffset+Constants.INDENT));
//}
{
ServerRef[] arr = data.getServerRefs();
for (int ii=0; arr!=null && ii<arr.length; ii++) {
sb.append(arr[ii].toXml(extraOffset+Constants.INDENT));
}
}
sb.append(data.writePropertiesXml(extraOffset+Constants.INDENT));
sb.append(offset).append("</").append(rootTag).append(">");
return sb.toString();
}
/**
* A human readable name of this factory
* @return "ConnectQosSaxFactory"
*/
public String getName() {
return "ConnectQosSaxFactory";
}
}