001: /**
002: * $Id: Configuration.java,v 1.9 2005/10/04 23:26:02 sorensen Exp $
003: * Copyright 2002 Sun Microsystems, Inc. All
004: * rights reserved. Use of this product is subject
005: * to license terms. Federal Acquisitions:
006: * Commercial Software -- Government Users
007: * Subject to Standard License Terms and
008: * Conditions.
009: *
010: * Sun, Sun Microsystems, the Sun logo, and iPlanet
011: * are trademarks or registered trademarks of Sun Microsystems,
012: * Inc. in the United States and other countries.
013: */package com.sun.ssoadapter.config;
014:
015: import java.io.UnsupportedEncodingException;
016: import java.util.*;
017: import java.net.*;
018: import java.lang.reflect.*; //import javax.servlet.http.*;
019: import javax.mail.URLName;
020:
021: //import javax.servlet.http.*;
022: import com.iplanet.sso.*;
023: import com.sun.portal.log.common.PortalLogger;
024: import com.sun.ssoadapter.SSOAdapterLogger;
025: import java.util.logging.Level;
026: import java.util.logging.Logger;
027:
028: /**
029: * This class acts as a container for either raw, or aggregated configuration
030: * information.
031: * <p>A "configuration" is a collection of information comprised of
032: * the following:
033: * <ul>
034: * <li>A HashMap of <code>String</code> arrays.
035: * <li>A configuration name.
036: * <li>A configuration type.
037: * <li>A list of "listeners" to be notified in the event of a change
038: * in the data in the HashMap.
039: * </ul>
040: * <p>Constructors are provided such that an empty <code>Configuration</code>
041: * object may be instantiated, or may be initialized via a URL string
042: * representation. Note that <code>Configuration</code> objects
043: * have no direct linkage to a storage/retrieval mechanism, i.e.,
044: * changes in a particular <code>Configuration</code> object are not
045: * automatically reflected and/or reconciled to any form of persistent
046: * database, e.g., a directory.
047: * <p>Methods are provided, appropriate for the manipulation of information
048: * contained within a <code>Configuration</code> object.
049: *
050: * @see com.sun.ssoadapter.config.ConfigurationFactory
051: * @see com.sun.ssoadapter.config.ConfigurationListener
052: * @see com.sun.ssoadapter.config.ConfigurationConstants
053: * @see com.sun.ssoadapter.config.ConfigurationException
054: *
055: */
056:
057: public class Configuration implements ConfigurationConstants {
058:
059: //
060: // Configurations are stored as a HashMap of String arrays,
061: // keyed by configuration property name.
062: //
063: private HashMap configHash = new HashMap(10);
064:
065: private String configName = null;
066: private String configDesc = null;
067: private Vector listenerVector = new Vector(2);
068: private Vector userPropertyList = new Vector();
069: private Vector encodedPropertyList = new Vector();
070:
071: //private Logger logger = SSOAdapterLogger.getLogger("com.sun.portal.ssoadapter.mbeans");
072: public static String ENCODING = "UTF-8";
073:
074: /**
075: * Constructs an empty <code>Configuration</code>.
076: * <ul>
077: * <li>An empty HashMap is created.
078: * <li>The configuration name is set to <code>null</code>.
079: * <li>The configuration type is set to <code>null</code>.
080: * <li>The list of listeners is empty.
081: * </ul>
082: */
083: public Configuration() {
084: }
085:
086: /**
087: * Constructs a Configuration, based on a URL string.
088: * <p>Configuration information is derived from
089: * the conventional syntactic components of a URL,
090: * and includes all parameters that may be
091: * present in a query string. The standard configuration
092: * properties that are extracted from the URL and stored within the
093: * HashMap are:
094: * <ul>
095: * <li>protocol
096: * <li>host
097: * <li>port
098: * <li>uid
099: * <li>password
100: * </ul>
101: * <p>Two query string parameters are treated specially:
102: * <ul>
103: * <li>configName
104: * <li>configDesc
105: * </ul>
106: * These values are stored within the <code>Configuration</code>
107: * object, but not within the HashMap.
108: */
109: public Configuration(String configURLString) {
110: URLName configURL = new URLName(configURLString);
111: String val = null;
112:
113: //
114: // Extract query parameters...
115: //
116: String queryString = null;
117: int qi = configURLString.indexOf('?');
118: if (qi >= 0) {
119: queryString = configURLString.substring(qi + 1);
120: }
121: if (queryString != null) {
122: // Get each query parameter
123: HashMap queryHash = new HashMap();
124: StringTokenizer queryST = new StringTokenizer(queryString,
125: "&");
126: while (queryST.hasMoreElements()) {
127: String att = queryST.nextToken();
128: int ei = att.indexOf('=');
129: if (ei >= 0) {
130: // Add this value to att's array of values
131: String name = att.substring(0, ei);
132: String value = att.substring(ei + 1);
133: try {
134: // Need to decode the name
135: if (name != null)
136: name = URLDecoder.decode(name, ENCODING);
137: // Need to decode the value
138: if (value != null)
139: value = URLDecoder.decode(value, ENCODING);
140: } catch (UnsupportedEncodingException uee) {
141: // Not sure how to log this message regardless of if used by the mbean or other parts of the SSOAdapter
142: //logger.log(Level.WARNING, "UnsupportedEncodingException when using "+ENCODING);
143: }
144: String[] newValArray = null;
145: if (queryHash.containsKey(name)) {
146: String[] valArray = (String[]) queryHash
147: .get(name);
148: if (valArray != null) {
149: newValArray = new String[valArray.length + 1];
150: for (int i = 0; i < valArray.length; i++) {
151: newValArray[i] = valArray[i];
152: }
153: newValArray[valArray.length] = value;
154: } else {
155: newValArray = new String[1];
156: newValArray[0] = value;
157: }
158: } else {
159: newValArray = new String[1];
160: newValArray[0] = value;
161: }
162: queryHash.put(name, newValArray);
163: }
164: }
165: if (queryHash != null) {
166: configHash.putAll(queryHash);
167: }
168: }
169:
170: //
171: // Extract configName and configDesc...
172: //
173: String[] configNameArray = (String[]) configHash
174: .get(PROP_CONFIG_NAME_NAME);
175: if (configNameArray != null) {
176: configName = configNameArray[0];
177: configHash.remove(PROP_CONFIG_NAME_NAME);
178: }
179:
180: String[] configDescArray = (String[]) configHash
181: .get(PROP_CONFIG_DESC_NAME);
182: if (configDescArray != null) {
183: configDesc = configDescArray[0];
184: configHash.remove(PROP_CONFIG_DESC_NAME);
185: }
186:
187: //
188: // Extract well known URL properties...
189: //
190:
191: val = configURL.getHost();
192: if ((val != null) && (val.length() > 0)) {
193: String[] arrayVal = { val };
194: if ((arrayVal != null) && (arrayVal[0].indexOf("=") == -1)) {
195: configHash.put(PROP_HOST_NAME, arrayVal);
196: }
197: }
198: int iPort = configURL.getPort();
199: if (iPort >= 0) {
200: String[] arrayVal = { Integer.toString(iPort) };
201: configHash.put(PROP_PORT_NAME, arrayVal);
202: }
203: val = configURL.getProtocol();
204: if (val != null) {
205: String[] arrayVal = { val };
206: configHash.put(PROP_PROTOCOL_NAME, arrayVal);
207: }
208: val = configURL.getUsername();
209: if (val != null) {
210: String[] arrayVal = { val };
211: configHash.put(PROP_UID_NAME, arrayVal);
212: }
213: val = configURL.getPassword();
214: if (val != null) {
215: String[] arrayVal = { val };
216: configHash.put(PROP_PASSWORD_NAME, arrayVal);
217: }
218:
219: }
220:
221: /**
222: * Returns the first element of a configuration property.
223: *
224: * @param key Identifies the name of the configuration property to get.
225: */
226: public String getProperty(String key) {
227: String[] props = (String[]) configHash.get(key);
228: return (props != null) ? props[0] : null;
229: }
230:
231: /**
232: * Returns the first element of a configuration property.
233: *
234: * @param key Identifies the name of the configuration property to get.
235: * @param defaultVal Specifies default value if property doesn't exist.
236: */
237: public String getProperty(String key, String defaultVal) {
238: String[] props = (String[]) configHash.get(key);
239: return (props != null) ? props[0] : defaultVal;
240: }
241:
242: /**
243: * Returns all values of a configuration property as a <code>String</code>
244: * array.
245: *
246: * @param key Identifies the name of the configuration property to get.
247: */
248: public String[] getPropertyArray(String key) {
249: return (String[]) configHash.get(key);
250: }
251:
252: /**
253: * Returns all properties as a <code>HashMap</code> of <code>String</code>
254: * arrays.
255: * <p>Modifications to this <code>HashMap</code> in effect change the
256: * properties associated with this <code>Configuration</code> object.
257: */
258: public HashMap getHashMap() {
259: return configHash;
260: }
261:
262: /*
263: * Returns a deep copy of properties as a <code>Properties</code> of
264: * <code>String</code>.
265: *
266: * @return Properties representing all configuration properties
267: */
268: public Properties getProperties() {
269: Properties props = new Properties();
270: HashMap cmap = getHashMap();
271:
272: for (Iterator it = cmap.keySet().iterator(); it.hasNext();) {
273: String key = (String) it.next();
274: String[] value = (String[]) cmap.get(key);
275: String val = (value != null) ? value[0] : null;
276:
277: props.setProperty(key, val);
278: }
279:
280: props.setProperty("configName", configName);
281:
282: if (configDesc != null) {
283: props.setProperty("configDesc", configDesc);
284: }
285:
286: return props;
287: }
288:
289: /**
290: * Sets a single valued property.
291: * <p>Once the value is set, all registered listeners are notified of
292: * the change.
293: *
294: * @param key Identifies the name of the configuration property to set.
295: * @param val The value to be set.
296: */
297: public void setProperty(String key, String value) {
298: String[] valArray = { value };
299: configHash.put(key, valArray);
300: notifyListeners();
301: return;
302: }
303:
304: /**
305: * Sets a multi-valued property.
306: * <p>Once the value is set, all registered listeners are notified of
307: * the change.
308: *
309: * @param key Identifies the name of the configuration property to set.
310: * @param val The values to be set.
311: */
312: public void setPropertyArray(String key, String[] values) {
313: configHash.put(key, values);
314: notifyListeners();
315: return;
316: }
317:
318: /**
319: * Replaces the <code>HashMap</code> of this <code>Configuration</code>
320: * object.
321: * <p>Once the <code>HashMap</code> is set, all registered listenters are
322: * notified of the change.
323: *
324: * @param hashMap The <code>HashMap</code> that this <code>Configuration</code>
325: * object will now reference.
326: */
327: public void setHashMap(HashMap hashMap) {
328: configHash = hashMap;
329: notifyListeners();
330: return;
331: }
332:
333: /**
334: * Removes a property.
335: * <p>Once the property is removed, all registered listeners are notified of
336: * the change.
337: *
338: * @param key Identifies the name of the configuration property to remove.
339: */
340: public void removeProperty(String key) {
341:
342: if ((key != null) && (configHash.containsKey(key))) {
343: configHash.remove(key);
344: notifyListeners();
345: }
346: return;
347: }
348:
349: /**
350: * Removes a property in a Property Array
351: * <p>Once the property is removed, all registered listeners are notified of
352: * the change.
353: *
354: * @param key Identifies the name of the configuration property array .
355: * @param keyset The keys to remove in the property array
356: */
357: public void removePropertyArray(String key, String[] keyset) {
358:
359: if ((key != null) && (keyset != null)
360: && (configHash.containsKey(key))) {
361: String[] vals = getPropertyArray(key);
362: List keysToRemoveList = Arrays.asList((String[]) keyset);
363:
364: if (vals != null) {
365: Vector valsList = new Vector(Arrays
366: .asList((String[]) vals));
367: valsList.removeAll(keysToRemoveList);
368: String[] finalList = (String[]) valsList
369: .toArray(new String[0]);
370: setPropertyArray(key, finalList);
371: notifyListeners();
372: }
373: }
374:
375: return;
376: }
377:
378: /**
379: * Returns the configuration name of this <code>Configuration</code> object.
380: */
381: public String getConfigurationName() {
382: return configName;
383: }
384:
385: /**
386: * Sets the configuration name of this <code>Configuration</code> object.
387: *
388: * @param name The configuration name.
389: */
390: public void setConfigurationName(String name) {
391: this .configName = name;
392: }
393:
394: /**
395: * Returns the configuration type of this <code>Configuration</code> object.
396: */
397: public String getConfigurationDescription() {
398: return configDesc;
399: }
400:
401: /**
402: * Sets the configuration type of this <code>Configuration</code> object.
403: *
404: * @param type The configuration type.
405: */
406: public void setConfigurationDescription(String desc) {
407: this .configDesc = desc;
408: }
409:
410: //
411: //TODO: Need to actually implement ConfigurationEvent class...
412: //
413:
414: /**
415: * Notifies the listeners when something changes.
416: */
417: private void notifyListeners() {
418: Vector removeVector = new Vector(2);
419: Enumeration listenerEnum = listenerVector.elements();
420: while (listenerEnum.hasMoreElements()) {
421: ConfigurationListener listener = (ConfigurationListener) listenerEnum
422: .nextElement();
423: try {
424: // For now, just pass null for ConfigurationEvent...
425: if (listener.configurationChanged(null)) {
426: removeVector.add(listener);
427: }
428: } catch (Exception e) {
429: }
430: }
431: Enumeration removeEnum = removeVector.elements();
432: while (removeEnum.hasMoreElements()) {
433: listenerVector.remove(removeEnum.nextElement());
434: }
435: }
436:
437: /**
438: * Registers a <code>ConfigurationListener</code>.
439: * <p>Listeners will be notified whenever any of the following
440: * methods are invoked upon this object:
441: * <ul>
442: * <li><code>setProperty()</code>
443: * <li><code>setPropertyArray()</code>
444: * <li><code>setHashMap()</code>
445: * </ul>
446: */
447: public void addListener(ConfigurationListener listener) {
448: if (listener != null) {
449: listenerVector.addElement(listener);
450: }
451: }
452:
453: /**
454: * Create a URL representation of this <code>Configuration</code> object.
455: * <p> A URL will be formed, with values derived from this
456: * <code>Configuration</code> object's <code>HashMap</code> as well
457: * configuration name and type. The URL will be of the following
458: * form:
459: * <pre><code><protocol>://<uid>:<password>@<host>:<port>/?<queryString></code></pre>
460: * <p>Where <queryString> contains parameters that define:
461: * <ul>
462: * <li>configName
463: * <li>configDesc
464: * </ul>
465: * as well as all other property values that are not expressed by the
466: * standard URL syntactic components.
467: * <p>Note: Owing to certain syntactic ambiguity of URL syntax in
468: * this application, if the configuration property "protocol" is not
469: * specified, a protocol of "undef" will appear in the resulting URL.
470: */
471: public String getConfigurationURL() {
472:
473: String[] hostArray = (String[]) configHash.get(PROP_HOST_NAME);
474: String[] protocolArray = (String[]) configHash
475: .get(PROP_PROTOCOL_NAME);
476: String[] portArray = (String[]) configHash.get(PROP_PORT_NAME);
477: String[] uidArray = (String[]) configHash.get(PROP_UID_NAME);
478: String[] passwordArray = (String[]) configHash
479: .get(PROP_PASSWORD_NAME);
480:
481: String host = (hostArray != null) ? hostArray[0] : null;
482: String protocol = (protocolArray != null) ? protocolArray[0]
483: : null;
484: String port = (portArray != null) ? portArray[0] : null;
485: String uid = (uidArray != null) ? uidArray[0] : null;
486: String password = (passwordArray != null) ? passwordArray[0]
487: : null;
488:
489: //
490: // Temporarily, remove the standard entries from the HashMap.
491: //
492: configHash.remove(PROP_HOST_NAME);
493: configHash.remove(PROP_PROTOCOL_NAME);
494: configHash.remove(PROP_PORT_NAME);
495: configHash.remove(PROP_UID_NAME);
496: configHash.remove(PROP_PASSWORD_NAME);
497:
498: //
499: // "protocol" must be specified...
500: //
501: if (protocol == null || protocol.length() == 0) {
502: protocol = PROP_UNDEF_PROTOCOL;
503: String[] tmpArray = { protocol };
504: protocolArray = tmpArray;
505: }
506:
507: //
508: // Create the "base" part of the URL...
509: //
510: int portInt;
511: try {
512: portInt = Integer.parseInt(port);
513: } catch (Exception ee) {
514: portInt = -1;
515: }
516:
517: // if the host is null and the port is valid, URLName will
518: // not add the port correctly. the solution is to set
519: // host to non-null.
520: //
521: if ((portInt != -1) && (host == null)) {
522: host = "";
523: }
524:
525: // if the uid is null and the password is valid, URLName will
526: // not add the password correctly. the solution is to set
527: // uid to non-null.
528: //
529: if ((password != null) && (uid == null)) {
530: uid = "";
531: }
532:
533: URLName configURL = new URLName(protocol, host, portInt, null,
534: uid, password);
535:
536: StringBuffer urlSB = new StringBuffer(configURL.toString());
537:
538: if (urlSB.charAt(urlSB.length() - 1) != '/') {
539: urlSB.append('/');
540: }
541:
542: //
543: // Start building query string, starting with configName & configDesc...
544: //
545: // This is required regardless of configName or configDesc
546: urlSB.append('?');
547:
548: // Make sure that the first parameter doesn't prepend an '&'
549: boolean isFirstParam = true;
550:
551: // Need to check for UnsupportedEncodingException, if found leave out failed encodings
552: try {
553: if (configName != null) {
554: urlSB.append(PROP_CONFIG_NAME_NAME).append('=').append(
555: URLEncoder.encode(configName, ENCODING));
556: isFirstParam = false;
557: }
558:
559: if (configDesc != null) {
560: if (!isFirstParam) {
561: urlSB.append('&');
562: } else {
563: isFirstParam = false;
564: }
565: urlSB.append(PROP_CONFIG_DESC_NAME).append('=').append(
566: URLEncoder.encode(configDesc, ENCODING));
567: }
568:
569: //
570: // Add all non-standard properties as query string parameters...
571: //
572: Iterator configIter = configHash.keySet().iterator();
573: while (configIter.hasNext()) {
574: String propName = (String) configIter.next();
575: String[] propVals = (String[]) configHash.get(propName);
576: if (propVals == null || propVals.length == 0)
577: continue;
578:
579: for (int i = 0; i < propVals.length; i++) {
580: //logger.log(Level.FINEST, "Building urlstring with name:"+propName+" value:"+propVals[i]);
581: if (!isFirstParam) {
582: urlSB.append('&');
583: } else {
584: isFirstParam = false;
585: }
586: urlSB.append(URLEncoder.encode(propName, ENCODING))
587: .append('=').append(
588: URLEncoder.encode(propVals[i],
589: ENCODING));
590: }
591: }
592: } catch (UnsupportedEncodingException uee) {
593: // Not sure how to log this message regardless of if used by the mbean or other parts of the SSOAdapter
594: //logger.log(Level.WARNING, "UnsupportedEncodingException when using "+ENCODING);
595: }
596: //
597: // Now put the standard properties back...
598: //
599: if (hostArray != null)
600: configHash.put(PROP_HOST_NAME, hostArray);
601: if (protocolArray != null)
602: configHash.put(PROP_PROTOCOL_NAME, protocolArray);
603: if (portArray != null)
604: configHash.put(PROP_PORT_NAME, portArray);
605: if (uidArray != null)
606: configHash.put(PROP_UID_NAME, uidArray);
607: if (passwordArray != null)
608: configHash.put(PROP_PASSWORD_NAME, passwordArray);
609:
610: return urlSB.toString();
611: }
612:
613: /**
614: * Returns the string representation of this <code>Configuration</code>
615: * object.
616: * <p>See <code>getConfigurationURL()</code>.
617: */
618: public String toString() {
619: return getConfigurationURL();
620: }
621:
622: public void setAsUserProperty(String key) {
623: userPropertyList.addElement(key);
624: }
625:
626: public void setAsEncodedProperty(String key) {
627: encodedPropertyList.addElement(key);
628: }
629:
630: public List getUserPropertiesList() {
631: return userPropertyList;
632: }
633:
634: public List getEncodedPropertiesList() {
635: return encodedPropertyList;
636: }
637:
638: }
|