/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.mq.sm.file;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import javax.jms.InvalidClientIDException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import org.jboss.system.server.ServerConfigLocator;
import org.jboss.mq.DurableSubscriptionID;
import org.jboss.mq.SpyTopic;
import org.jboss.mq.SpyJMSException;
import org.jboss.mq.xml.XElement;
import org.jboss.mq.xml.XElementException;
import org.jboss.mq.sm.StateManager;
import org.jboss.mq.sm.AbstractStateManager;
/**
* A state manager that allowed durable subscriptions to be dynamically
* created if configured to support it. Otherwise backward compatible with
* the old StateManager.
*
*
Backed by an XML file.
*
*
Example file format:
*
john
needle
DurableSubscriberExample
john
needle
myDurableSub
TestTopic...
*
*
* @jmx:mbean extends="org.jboss.mq.sm.AbstractStateManagerMBean"
*
* @author Norbert Lataille
* @author Hiram Chirino
* @author Peter Antman
* @version $Revision: 57198 $
*/
public class DynamicStateManager extends AbstractStateManager implements DynamicStateManagerMBean
{
class DynamicDurableSubscription extends DurableSubscription
{
XElement element;
public DynamicDurableSubscription(XElement element) throws XElementException
{
super(
element.getField("ClientID"),
element.getField("Name"),
element.getField("TopicName"),
element.getOptionalField("Selector"));
this.element = element;
}
XElement getElement()
{
return element;
}
// Hm, I don't think we should mutate it without a sync.
//public void setTopic(String topic) {
// this.topic = topic;
// element.setField("Topic", topic);
// }
}
/**
* Do we have a security manager.
*
* By setting this to false, we may emulate the old behaviour of
* the state manager and let it autenticate connections.
*/
boolean hasSecurityManager = true;
XElement stateConfig = new XElement("StateManager"); //So sync allways work
/** State file is relateive to systemConfigURL. */
private String stateFile = "jbossmq-state.xml";
private URL systemConfigURL;
public DynamicStateManager()
{
}
//
// MBean methods
//
public StateManager getInstance()
{
return this;
}
protected void createService() throws Exception
{
// Get the system configuration URL
systemConfigURL = ServerConfigLocator.locate().getServerConfigURL();
}
public void startService() throws Exception
{
loadConfig();
}
/**
* Show the current configuration.
*/
public String displayStateConfig() throws Exception
{
return stateConfig.toString();
}
/**
* Set the name of the statefile.
*
* @jmx:managed-attribute
*
* @param newStateFile java.lang.String
*/
public void setStateFile(String newStateFile)
{
stateFile = newStateFile.trim();
}
/**
* Get name of file.
*
* @jmx:managed-attribute
*
* @return java.lang.String
*/
public String getStateFile()
{
return stateFile;
}
/**
* @jmx:managed-attribute
*/
public boolean hasSecurityManager()
{
return hasSecurityManager;
}
/**
* @jmx:managed-attribute
*/
public void setHasSecurityManager(boolean hasSecurityManager)
{
this.hasSecurityManager = hasSecurityManager;
}
/**
* @jmx:managed-operation
*/
public void loadConfig() throws IOException, XElementException
{
URL configURL = new URL(systemConfigURL, stateFile);
if (log.isDebugEnabled())
{
log.debug("Loading config from: " + configURL);
}
InputStream in = new BufferedInputStream(configURL.openStream());
try
{
synchronized (stateConfig)
{
stateConfig = XElement.createFrom(in);
}
}
finally
{
in.close();
}
}
/**
* @jmx:managed-operation
*/
public void saveConfig() throws IOException
{
URL configURL = new URL(systemConfigURL, stateFile);
if (configURL.getProtocol().equals("file"))
{
File file = new File(configURL.getFile());
if (log.isDebugEnabled())
{
log.debug("Saving config to: " + file);
}
PrintStream stream = new PrintStream(new FileOutputStream(file));
try
{
synchronized (stateConfig)
{
stream.print(stateConfig.toXML(true));
}
}
finally
{
stream.close();
}
}
else
{
log.error("Can not save configuration to non-file URL: " + configURL);
}
}
//
// Callback methods from AbstractStateManager
//
/**
* Return preconfigured client id. Only if hasSecurityManager is false will
* a password be required to get the clientID and will the method throw
* a JMSSecurityException if the clientID was not found.
*/
protected String getPreconfClientId(String login, String passwd) throws JMSException
{
try
{
synchronized (stateConfig)
{
Enumeration enumeration = stateConfig.getElementsNamed("Users/User");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
String name = element.getField("Name");
if (!name.equals(login))
{
continue; // until user is found
}
// Onlyn check password if we do not have a security manager
if (!hasSecurityManager)
{
String pw = element.getField("Password");
if (!passwd.equals(pw))
{
throw new JMSSecurityException("Bad password");
}
}
String clientId = null;
if (element.containsField("Id"))
{
clientId = element.getField("Id");
}
//if (clientId != null)
return clientId;
}
if (!hasSecurityManager)
throw new JMSSecurityException("This user does not exist");
else
return null;
}
}
catch (XElementException e)
{
log.error(e);
throw new JMSException("Invalid server user configuration.");
}
}
/**
* Search for a configurated durable subscription.
*/
protected DurableSubscription getDurableSubscription(DurableSubscriptionID sub) throws JMSException
{
boolean debug = log.isDebugEnabled();
//Set the known Ids
try
{
synchronized (stateConfig)
{
Enumeration enumeration = stateConfig.getElementsNamed("DurableSubscriptions/DurableSubscription");
while (enumeration.hasMoreElements())
{
// Match ID
XElement dur = (XElement) enumeration.nextElement();
if (dur.containsField("ClientID") && dur.getField("ClientID").equals(sub.getClientID()))
{
// Check if this one has a DurableSubname that match
if (dur.getField("Name").equals(sub.getSubscriptionName()))
{
// We have a match
if (debug)
log.debug("Found a matching ClientID configuration section.");
return new DynamicDurableSubscription(dur);
}
}
}
// Nothing found
return null;
}
}
catch (XElementException e)
{
JMSException newE = new SpyJMSException("Could not find durable subscription");
newE.setLinkedException(e);
throw newE;
}
}
/**
* Check if the clientID belonges to a preconfigured user. If this
* is the case, a InvalidClientIDException will be raised.
*/
protected void checkLoggedOnClientId(String clientID) throws JMSException
{
synchronized (stateConfig)
{
Enumeration enumeration = stateConfig.getElementsNamed("Users/User");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
try
{
if (element.containsField("Id") && element.getField("Id").equals(clientID))
{
throw new InvalidClientIDException("This loggedOnClientIds is password protected !");
}
}
catch (XElementException ignore)
{
}
}
}
}
protected void saveDurableSubscription(DurableSubscription ds) throws JMSException
{
try
{
synchronized (stateConfig)
{
// Or logic here is simply this, if we get a DynamicDurableSubscription
// Its reconfiguration, if not it is new
if (ds instanceof DynamicDurableSubscription)
{
XElement s = ((DynamicDurableSubscription) ds).getElement();
if (s != null)
{
s.setField("TopicName", ds.getTopic()); //In case it changed.
s.setOptionalField("Selector", ds.getSelector()); //In case it changed.
}
else
{
throw new JMSException("Can not save a null subscription");
}
}
else
{
XElement dur = stateConfig.getElement("DurableSubscriptions");
XElement subscription = new XElement("DurableSubscription");
subscription.addField("ClientID", ds.getClientID());
subscription.addField("Name", ds.getName());
subscription.addField("TopicName", ds.getTopic());
subscription.setOptionalField("Selector", ds.getSelector());
dur.addElement(subscription);
}
saveConfig();
}
}
catch (XElementException e)
{
JMSException newE = new SpyJMSException("Could not save the durable subscription");
newE.setLinkedException(e);
throw newE;
}
catch (IOException e)
{
JMSException newE = new SpyJMSException("Could not save the durable subscription");
newE.setLinkedException(e);
throw newE;
}
}
protected void removeDurableSubscription(DurableSubscription ds) throws JMSException
{
try
{
// We only remove if it was our own dur sub.
synchronized (stateConfig)
{
XElement s = ((DynamicDurableSubscription) ds).getElement();
if (s != null)
{
s.removeFromParent();
saveConfig();
}
else
{
throw new JMSException("Can not remove a null subscription");
}
}
}
catch (XElementException e)
{
JMSException newE = new SpyJMSException("Could not remove the durable subscription");
newE.setLinkedException(e);
throw newE;
}
catch (IOException e)
{
JMSException newE = new SpyJMSException("Could not remove the durable subscription");
newE.setLinkedException(e);
throw newE;
}
}
public Collection getDurableSubscriptionIdsForTopic(SpyTopic topic) throws JMSException
{
Collection durableSubs = new ArrayList();
try
{
synchronized (stateConfig)
{
Enumeration enumeration = stateConfig.getElementsNamed("DurableSubscriptions/DurableSubscription");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
String clientId = element.getField("ClientID");
String name = element.getField("Name");
String topicName = element.getField("TopicName");
String selector = element.getOptionalField("Selector");
if (topic.getName().equals(topicName))
{
durableSubs.add(new DurableSubscriptionID(clientId, name, selector));
} // end of if ()
}
}
}
catch (XElementException e)
{
JMSException jmse = new JMSException("Error in statemanager xml");
jmse.setLinkedException(e);
throw jmse;
} // end of try-catch
return durableSubs;
}
//
// The methods that allow dynamic edititing of state manager.
//
/**
* @jmx:managed-operation
*/
public void addUser(String name, String password, String preconfID) throws Exception
{
if (findUser(name) != null)
throw new Exception("Can not add, user exist");
XElement users = stateConfig.getElement("Users");
XElement user = new XElement("User");
user.addField("Name", name);
user.addField("Password", password);
if (preconfID != null)
user.addField("Id", preconfID);
users.addElement(user);
saveConfig();
}
/**
* @jmx:managed-operation
*/
public void removeUser(String name) throws Exception
{
XElement user = findUser(name);
if (user == null)
throw new Exception("Cant remove user that does not exist");
user.removeFromParent();
// We should also remove the user from any roles it belonges to
String[] roles = getRoles(name);
if (roles != null)
{
for (int i = 0; i < roles.length; i++)
{
try
{
removeUserFromRole(roles[i], name);
}
catch (Exception ex)
{
//Just move on
}
}
}
saveConfig();
}
/**
* @jmx:managed-operation
*/
public void addRole(String name) throws Exception
{
if (findRole(name) != null)
throw new Exception("Cant add role, it already exists");
XElement roles = stateConfig.getElement("Roles");
XElement role = new XElement("Role");
role.setAttribute("name", name);
roles.addElement(role);
saveConfig();
}
/**
* @jmx:managed-operation
*/
public void removeRole(String name) throws Exception
{
XElement role = findRole(name);
if (role == null)
throw new Exception("Cant remove role that does not exist");
role.removeFromParent();
saveConfig();
}
// FIXME; no sanity check that the "real" user does exist.
/**
* @jmx:managed-operation
*/
public void addUserToRole(String roleName, String user) throws Exception
{
XElement role = findRole(roleName);
if (role == null)
throw new Exception("Cant add to role that does not exist");
if (findUser(user) == null)
throw new Exception("Cant add user to role, user does to exist");
if (findUserInRole(role, user) != null)
throw new Exception("Cant add user to role, user already part of role");
// FIXME; here I am not shure how XElement work
XElement u = new XElement("UserName");
u.setValue(user);
role.addElement(u);
saveConfig();
}
/**
* @jmx:managed-operation
*/
public void removeUserFromRole(String roleName, String user) throws Exception
{
XElement role = findRole(roleName);
if (role == null)
throw new Exception("Cant remove user from role that does not exist");
XElement u = findUserInRole(role, user);
if (u == null)
throw new Exception("Cant remove user from role, user does not exist");
u.removeFromParent();
saveConfig();
}
protected XElement findUser(String user) throws Exception
{
Enumeration enumeration = stateConfig.getElementsNamed("Users/User");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
if (element.getField("Name").equals(user))
return element;
}
return null;
}
protected XElement findRole(String role) throws Exception
{
Enumeration enumeration = stateConfig.getElementsNamed("Roles/Role");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
if (element.getAttribute("name").equals(role))
return element;
}
return null;
}
protected XElement findUserInRole(XElement role, String user) throws Exception
{
Enumeration enumeration = role.getElementsNamed("UserName");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
if (user.equals(element.getValue()))
return element;
}
return null;
}
//
// Methods to support LoginModule
//
/**
* We currently only support one Group type Roles. The role named
* returned should typically be put into a Roles Group principal.
*/
public String[] getRoles(String user) throws Exception
{
ArrayList roles = new ArrayList();
Enumeration enumeration = stateConfig.getElementsNamed("Roles/Role");
while (enumeration.hasMoreElements())
{
XElement element = (XElement) enumeration.nextElement();
XElement u = findUserInRole(element, user);
if (u != null)
roles.add(element.getAttribute("name"));
}
return (String[]) roles.toArray(new String[roles.size()]);
}
/**
* Validate the user/password combination. A null inputPassword will
* allways reurn false.
*/
public boolean validatePassword(String user, String inputPassword) throws Exception
{
boolean valid = false;
XElement u = findUser(user);
if (u != null)
{
String pw = u.getField("Password");
if (inputPassword != null && inputPassword.equals(pw))
valid = true;
}
return valid;
}
//
// Helper methods
//
} // DynamicStateManager