001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mq.sm.file;
023:
024: import java.io.File;
025: import java.io.FileOutputStream;
026: import java.io.PrintStream;
027: import java.io.InputStream;
028: import java.io.BufferedInputStream;
029: import java.io.IOException;
030: import java.net.URL;
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.Enumeration;
034:
035: import javax.jms.InvalidClientIDException;
036: import javax.jms.JMSException;
037: import javax.jms.JMSSecurityException;
038:
039: import org.jboss.system.server.ServerConfigLocator;
040: import org.jboss.mq.DurableSubscriptionID;
041: import org.jboss.mq.SpyTopic;
042: import org.jboss.mq.SpyJMSException;
043: import org.jboss.mq.xml.XElement;
044: import org.jboss.mq.xml.XElementException;
045:
046: import org.jboss.mq.sm.StateManager;
047: import org.jboss.mq.sm.AbstractStateManager;
048:
049: /**
050: * A state manager that allowed durable subscriptions to be dynamically
051: * created if configured to support it. Otherwise backward compatible with
052: * the old StateManager.
053: *
054: * <p>Backed by an XML file.
055: *
056: * <p>Example file format:
057: * <xmp>
058: <StateManager>
059: <Users>
060: <User>
061: <Name>john</Name>
062: <Password>needle</Password>
063: <Id>DurableSubscriberExample</Id><!-- optional -->
064: </User>
065: </Users>
066:
067: <Roles>
068: <Role name="guest">
069: <UserName>john</UserName>
070: </Role>
071:
072: </Roles>
073:
074: <DurableSubscriptions>
075: <DurableSubscription>
076: <ClientID>needle</ClientID>
077: <Name>myDurableSub</Name>
078: <TopicName>TestTopic...</TopicName>
079: </DurableSubscription>
080:
081: </DurableSubscriptions>
082: </StateManager>
083: * </xmp>
084: *
085: * @jmx:mbean extends="org.jboss.mq.sm.AbstractStateManagerMBean"
086: *
087: * @author <a href="Norbert.Lataille@m4x.org">Norbert Lataille</a>
088: * @author <a href="hiram.chirino@jboss.org">Hiram Chirino</a>
089: * @author <a href="pra@tim.se">Peter Antman</a>
090: * @version $Revision: 57198 $
091: */
092: public class DynamicStateManager extends AbstractStateManager implements
093: DynamicStateManagerMBean {
094: class DynamicDurableSubscription extends DurableSubscription {
095: XElement element;
096:
097: public DynamicDurableSubscription(XElement element)
098: throws XElementException {
099: super (element.getField("ClientID"), element
100: .getField("Name"), element.getField("TopicName"),
101: element.getOptionalField("Selector"));
102: this .element = element;
103: }
104:
105: XElement getElement() {
106: return element;
107: }
108:
109: // Hm, I don't think we should mutate it without a sync.
110: //public void setTopic(String topic) {
111: // this.topic = topic;
112: // element.setField("Topic", topic);
113: // }
114:
115: }
116:
117: /**
118: * Do we have a security manager.
119: *
120: * By setting this to false, we may emulate the old behaviour of
121: * the state manager and let it autenticate connections.
122: */
123: boolean hasSecurityManager = true;
124:
125: XElement stateConfig = new XElement("StateManager"); //So sync allways work
126:
127: /** State file is relateive to systemConfigURL. */
128: private String stateFile = "jbossmq-state.xml";
129: private URL systemConfigURL;
130:
131: public DynamicStateManager() {
132:
133: }
134:
135: //
136: // MBean methods
137: //
138: public StateManager getInstance() {
139: return this ;
140: }
141:
142: protected void createService() throws Exception {
143: // Get the system configuration URL
144: systemConfigURL = ServerConfigLocator.locate()
145: .getServerConfigURL();
146: }
147:
148: public void startService() throws Exception {
149: loadConfig();
150: }
151:
152: /**
153: * Show the current configuration.
154: */
155: public String displayStateConfig() throws Exception {
156: return stateConfig.toString();
157: }
158:
159: /**
160: * Set the name of the statefile.
161: *
162: * @jmx:managed-attribute
163: *
164: * @param newStateFile java.lang.String
165: */
166: public void setStateFile(String newStateFile) {
167: stateFile = newStateFile.trim();
168: }
169:
170: /**
171: * Get name of file.
172: *
173: * @jmx:managed-attribute
174: *
175: * @return java.lang.String
176: */
177: public String getStateFile() {
178: return stateFile;
179: }
180:
181: /**
182: * @jmx:managed-attribute
183: */
184: public boolean hasSecurityManager() {
185: return hasSecurityManager;
186: }
187:
188: /**
189: * @jmx:managed-attribute
190: */
191: public void setHasSecurityManager(boolean hasSecurityManager) {
192: this .hasSecurityManager = hasSecurityManager;
193: }
194:
195: /**
196: * @jmx:managed-operation
197: */
198: public void loadConfig() throws IOException, XElementException {
199: URL configURL = new URL(systemConfigURL, stateFile);
200: if (log.isDebugEnabled()) {
201: log.debug("Loading config from: " + configURL);
202: }
203:
204: InputStream in = new BufferedInputStream(configURL.openStream());
205: try {
206: synchronized (stateConfig) {
207: stateConfig = XElement.createFrom(in);
208: }
209: } finally {
210: in.close();
211: }
212: }
213:
214: /**
215: * @jmx:managed-operation
216: */
217: public void saveConfig() throws IOException {
218: URL configURL = new URL(systemConfigURL, stateFile);
219:
220: if (configURL.getProtocol().equals("file")) {
221: File file = new File(configURL.getFile());
222: if (log.isDebugEnabled()) {
223: log.debug("Saving config to: " + file);
224: }
225:
226: PrintStream stream = new PrintStream(new FileOutputStream(
227: file));
228: try {
229: synchronized (stateConfig) {
230: stream.print(stateConfig.toXML(true));
231: }
232: } finally {
233: stream.close();
234: }
235: } else {
236: log.error("Can not save configuration to non-file URL: "
237: + configURL);
238: }
239: }
240:
241: //
242: // Callback methods from AbstractStateManager
243: //
244: /**
245: * Return preconfigured client id. Only if hasSecurityManager is false will
246: * a password be required to get the clientID and will the method throw
247: * a JMSSecurityException if the clientID was not found.
248: */
249: protected String getPreconfClientId(String login, String passwd)
250: throws JMSException {
251: try {
252: synchronized (stateConfig) {
253:
254: Enumeration enumeration = stateConfig
255: .getElementsNamed("Users/User");
256: while (enumeration.hasMoreElements()) {
257: XElement element = (XElement) enumeration
258: .nextElement();
259: String name = element.getField("Name");
260: if (!name.equals(login)) {
261: continue; // until user is found
262: }
263:
264: // Onlyn check password if we do not have a security manager
265: if (!hasSecurityManager) {
266: String pw = element.getField("Password");
267: if (!passwd.equals(pw)) {
268: throw new JMSSecurityException(
269: "Bad password");
270: }
271: }
272:
273: String clientId = null;
274: if (element.containsField("Id")) {
275: clientId = element.getField("Id");
276: }
277:
278: //if (clientId != null)
279: return clientId;
280: }
281: if (!hasSecurityManager)
282: throw new JMSSecurityException(
283: "This user does not exist");
284: else
285: return null;
286: }
287: } catch (XElementException e) {
288: log.error(e);
289: throw new JMSException("Invalid server user configuration.");
290: }
291: }
292:
293: /**
294: * Search for a configurated durable subscription.
295: */
296: protected DurableSubscription getDurableSubscription(
297: DurableSubscriptionID sub) throws JMSException {
298: boolean debug = log.isDebugEnabled();
299:
300: //Set the known Ids
301: try {
302: synchronized (stateConfig) {
303:
304: Enumeration enumeration = stateConfig
305: .getElementsNamed("DurableSubscriptions/DurableSubscription");
306: while (enumeration.hasMoreElements()) {
307:
308: // Match ID
309: XElement dur = (XElement) enumeration.nextElement();
310: if (dur.containsField("ClientID")
311: && dur.getField("ClientID").equals(
312: sub.getClientID())) {
313: // Check if this one has a DurableSubname that match
314: if (dur.getField("Name").equals(
315: sub.getSubscriptionName())) {
316: // We have a match
317: if (debug)
318: log
319: .debug("Found a matching ClientID configuration section.");
320: return new DynamicDurableSubscription(dur);
321: }
322:
323: }
324: }
325: // Nothing found
326: return null;
327: }
328: } catch (XElementException e) {
329: JMSException newE = new SpyJMSException(
330: "Could not find durable subscription");
331: newE.setLinkedException(e);
332: throw newE;
333: }
334: }
335:
336: /**
337: * Check if the clientID belonges to a preconfigured user. If this
338: * is the case, a InvalidClientIDException will be raised.
339: */
340: protected void checkLoggedOnClientId(String clientID)
341: throws JMSException {
342: synchronized (stateConfig) {
343:
344: Enumeration enumeration = stateConfig
345: .getElementsNamed("Users/User");
346: while (enumeration.hasMoreElements()) {
347: XElement element = (XElement) enumeration.nextElement();
348: try {
349: if (element.containsField("Id")
350: && element.getField("Id").equals(clientID)) {
351: throw new InvalidClientIDException(
352: "This loggedOnClientIds is password protected !");
353: }
354: } catch (XElementException ignore) {
355: }
356: }
357: }
358:
359: }
360:
361: protected void saveDurableSubscription(DurableSubscription ds)
362: throws JMSException {
363: try {
364: synchronized (stateConfig) {
365: // Or logic here is simply this, if we get a DynamicDurableSubscription
366: // Its reconfiguration, if not it is new
367: if (ds instanceof DynamicDurableSubscription) {
368: XElement s = ((DynamicDurableSubscription) ds)
369: .getElement();
370: if (s != null) {
371: s.setField("TopicName", ds.getTopic()); //In case it changed.
372: s
373: .setOptionalField("Selector", ds
374: .getSelector()); //In case it changed.
375: } else {
376: throw new JMSException(
377: "Can not save a null subscription");
378: }
379: } else {
380: XElement dur = stateConfig
381: .getElement("DurableSubscriptions");
382: XElement subscription = new XElement(
383: "DurableSubscription");
384: subscription.addField("ClientID", ds.getClientID());
385: subscription.addField("Name", ds.getName());
386: subscription.addField("TopicName", ds.getTopic());
387: subscription.setOptionalField("Selector", ds
388: .getSelector());
389: dur.addElement(subscription);
390: }
391: saveConfig();
392: }
393: } catch (XElementException e) {
394: JMSException newE = new SpyJMSException(
395: "Could not save the durable subscription");
396: newE.setLinkedException(e);
397: throw newE;
398: } catch (IOException e) {
399: JMSException newE = new SpyJMSException(
400: "Could not save the durable subscription");
401: newE.setLinkedException(e);
402: throw newE;
403: }
404: }
405:
406: protected void removeDurableSubscription(DurableSubscription ds)
407: throws JMSException {
408: try {
409: // We only remove if it was our own dur sub.
410: synchronized (stateConfig) {
411: XElement s = ((DynamicDurableSubscription) ds)
412: .getElement();
413: if (s != null) {
414: s.removeFromParent();
415: saveConfig();
416: } else {
417: throw new JMSException(
418: "Can not remove a null subscription");
419: }
420: }
421: } catch (XElementException e) {
422: JMSException newE = new SpyJMSException(
423: "Could not remove the durable subscription");
424: newE.setLinkedException(e);
425: throw newE;
426: } catch (IOException e) {
427: JMSException newE = new SpyJMSException(
428: "Could not remove the durable subscription");
429: newE.setLinkedException(e);
430: throw newE;
431: }
432: }
433:
434: public Collection getDurableSubscriptionIdsForTopic(SpyTopic topic)
435: throws JMSException {
436: Collection durableSubs = new ArrayList();
437: try {
438: synchronized (stateConfig) {
439:
440: Enumeration enumeration = stateConfig
441: .getElementsNamed("DurableSubscriptions/DurableSubscription");
442: while (enumeration.hasMoreElements()) {
443: XElement element = (XElement) enumeration
444: .nextElement();
445:
446: String clientId = element.getField("ClientID");
447: String name = element.getField("Name");
448: String topicName = element.getField("TopicName");
449: String selector = element
450: .getOptionalField("Selector");
451: if (topic.getName().equals(topicName)) {
452: durableSubs.add(new DurableSubscriptionID(
453: clientId, name, selector));
454: } // end of if ()
455:
456: }
457: }
458: } catch (XElementException e) {
459: JMSException jmse = new JMSException(
460: "Error in statemanager xml");
461: jmse.setLinkedException(e);
462: throw jmse;
463: } // end of try-catch
464: return durableSubs;
465: }
466:
467: //
468: // The methods that allow dynamic edititing of state manager.
469: //
470:
471: /**
472: * @jmx:managed-operation
473: */
474: public void addUser(String name, String password, String preconfID)
475: throws Exception {
476: if (findUser(name) != null)
477: throw new Exception("Can not add, user exist");
478:
479: XElement users = stateConfig.getElement("Users");
480: XElement user = new XElement("User");
481: user.addField("Name", name);
482: user.addField("Password", password);
483: if (preconfID != null)
484: user.addField("Id", preconfID);
485: users.addElement(user);
486: saveConfig();
487: }
488:
489: /**
490: * @jmx:managed-operation
491: */
492: public void removeUser(String name) throws Exception {
493: XElement user = findUser(name);
494: if (user == null)
495: throw new Exception("Cant remove user that does not exist");
496:
497: user.removeFromParent();
498:
499: // We should also remove the user from any roles it belonges to
500: String[] roles = getRoles(name);
501: if (roles != null) {
502: for (int i = 0; i < roles.length; i++) {
503: try {
504: removeUserFromRole(roles[i], name);
505: } catch (Exception ex) {
506: //Just move on
507: }
508: }
509: }
510:
511: saveConfig();
512: }
513:
514: /**
515: * @jmx:managed-operation
516: */
517: public void addRole(String name) throws Exception {
518: if (findRole(name) != null)
519: throw new Exception("Cant add role, it already exists");
520:
521: XElement roles = stateConfig.getElement("Roles");
522: XElement role = new XElement("Role");
523: role.setAttribute("name", name);
524: roles.addElement(role);
525: saveConfig();
526: }
527:
528: /**
529: * @jmx:managed-operation
530: */
531: public void removeRole(String name) throws Exception {
532: XElement role = findRole(name);
533: if (role == null)
534: throw new Exception("Cant remove role that does not exist");
535:
536: role.removeFromParent();
537: saveConfig();
538: }
539:
540: // FIXME; no sanity check that the "real" user does exist.
541: /**
542: * @jmx:managed-operation
543: */
544: public void addUserToRole(String roleName, String user)
545: throws Exception {
546: XElement role = findRole(roleName);
547: if (role == null)
548: throw new Exception("Cant add to role that does not exist");
549:
550: if (findUser(user) == null)
551: throw new Exception(
552: "Cant add user to role, user does to exist");
553:
554: if (findUserInRole(role, user) != null)
555: throw new Exception(
556: "Cant add user to role, user already part of role");
557: // FIXME; here I am not shure how XElement work
558: XElement u = new XElement("UserName");
559: u.setValue(user);
560: role.addElement(u);
561: saveConfig();
562: }
563:
564: /**
565: * @jmx:managed-operation
566: */
567: public void removeUserFromRole(String roleName, String user)
568: throws Exception {
569: XElement role = findRole(roleName);
570: if (role == null)
571: throw new Exception(
572: "Cant remove user from role that does not exist");
573:
574: XElement u = findUserInRole(role, user);
575: if (u == null)
576: throw new Exception(
577: "Cant remove user from role, user does not exist");
578: u.removeFromParent();
579: saveConfig();
580:
581: }
582:
583: protected XElement findUser(String user) throws Exception {
584: Enumeration enumeration = stateConfig
585: .getElementsNamed("Users/User");
586: while (enumeration.hasMoreElements()) {
587: XElement element = (XElement) enumeration.nextElement();
588: if (element.getField("Name").equals(user))
589: return element;
590: }
591: return null;
592: }
593:
594: protected XElement findRole(String role) throws Exception {
595: Enumeration enumeration = stateConfig
596: .getElementsNamed("Roles/Role");
597: while (enumeration.hasMoreElements()) {
598: XElement element = (XElement) enumeration.nextElement();
599: if (element.getAttribute("name").equals(role))
600: return element;
601: }
602: return null;
603: }
604:
605: protected XElement findUserInRole(XElement role, String user)
606: throws Exception {
607: Enumeration enumeration = role.getElementsNamed("UserName");
608: while (enumeration.hasMoreElements()) {
609: XElement element = (XElement) enumeration.nextElement();
610: if (user.equals(element.getValue()))
611: return element;
612: }
613: return null;
614: }
615:
616: //
617: // Methods to support LoginModule
618: //
619: /**
620: * We currently only support one Group type Roles. The role named
621: * returned should typically be put into a Roles Group principal.
622: */
623: public String[] getRoles(String user) throws Exception {
624: ArrayList roles = new ArrayList();
625: Enumeration enumeration = stateConfig
626: .getElementsNamed("Roles/Role");
627: while (enumeration.hasMoreElements()) {
628: XElement element = (XElement) enumeration.nextElement();
629: XElement u = findUserInRole(element, user);
630: if (u != null)
631: roles.add(element.getAttribute("name"));
632: }
633: return (String[]) roles.toArray(new String[roles.size()]);
634: }
635:
636: /**
637: * Validate the user/password combination. A null inputPassword will
638: * allways reurn false.
639: */
640: public boolean validatePassword(String user, String inputPassword)
641: throws Exception {
642: boolean valid = false;
643: XElement u = findUser(user);
644: if (u != null) {
645: String pw = u.getField("Password");
646: if (inputPassword != null && inputPassword.equals(pw))
647: valid = true;
648: }
649: return valid;
650: }
651:
652: //
653: // Helper methods
654: //
655:
656: } // DynamicStateManager
|