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.mx.util;
023:
024: import java.util.Hashtable;
025: import java.util.Iterator;
026: import java.util.StringTokenizer;
027:
028: import javax.management.MalformedObjectNameException;
029: import javax.management.ObjectName;
030:
031: /**
032: * Converts forbidden characters in the key and value of an object name
033: * to valid characters and back.
034: * <br>
035: * Character Conversion Table: (based on RFC 1738 style escapes<br>
036: * '%' => '%25' <br>
037: * '*' => '%2a' <br>
038: * ',' => '%2c' <br>
039: * ':' => '%3a' <br>
040: * '?' => '%3f' <br>
041: * '=' => '%3d' <br>
042: * '\'' => '%27' <br>
043: * '\"' => '%22' <br>
044: * <br>Thanx to William Hoyle for mention this
045: * <br><b>Attention:</b>When you have a comma in one of your property
046: * value then you have to use a <i>Hashtable</i> to provide the properties
047: * otherwise the property parsing will fail.
048: *
049: * @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>
050: * @author <a href="mailto:william.hoyle@jungledrum.co.nz">William Hoyle</a>
051: * @version $Revision: 61303 $
052: */
053: public class ObjectNameConverter {
054: /**
055: * Parses the given Object Name String representation and
056: * replaces any invalid characters in property keays and values with
057: * valid characters.
058: * </b>Attention:</b> Do not use this method when a property
059: * key or value contain a comma because then the parsing will fail.
060: * Please use the {@link #convert( java.lang.String, java.util.Hashtable )
061: * convert( String, Hashtable )} instead because the properties
062: * are already parsed (by you).
063: *
064: * @param pObjectName String representing an Object Name which must
065: * not contain a comman inside a property value
066: *
067: * @return Created Object Name with the converted keys and values
068: * of the given Object Name
069: *
070: * @throws javax.management.MalformedObjectNameException If the given Object Name
071: * is not correct
072: **/
073: public static ObjectName convert(String pObjectName)
074: throws MalformedObjectNameException {
075: if (pObjectName == null) {
076: throw new MalformedObjectNameException("null name");
077: }
078:
079: // REVIEW, is the following a hack?: It is in the spec for patterns
080: if (pObjectName.length() == 0) {
081: pObjectName = "*:*";
082: }
083:
084: int lIndex = pObjectName.indexOf(":");
085: if (lIndex < 0) {
086: throw new MalformedObjectNameException("missing domain");
087: }
088: String lDomain = pObjectName.substring(0, lIndex);
089: if ((lIndex + 1) < pObjectName.length()) {
090: return createObjectName(lDomain, pObjectName
091: .substring(lIndex + 1));
092: } else {
093: throw new MalformedObjectNameException("properties missing");
094: }
095: }
096:
097: /**
098: * Check the keys and values of the properties and convert invalid characters
099: *
100: * @param pDomainName Name of the Domain
101: * @param pProperites Hashtable containing the properties of the Object Name
102: *
103: * @return Created Object Name with the converted keays and values
104: *
105: * @throws javax.management.MalformedObjectNameException If the given Object Name
106: * is not correct
107: **/
108: public static ObjectName convert(String pDomainName,
109: Hashtable pProperties) throws MalformedObjectNameException {
110: if (pDomainName == null) {
111: throw new MalformedObjectNameException("missing domain");
112: }
113: if (pProperties == null || pProperties.size() == 0) {
114: throw new MalformedObjectNameException(
115: " null or empty properties");
116: }
117: return createObjectName(pDomainName, pProperties, false);
118: }
119:
120: /**
121: * Takes the properties from the given Object Name and convert
122: * special characters back
123: *
124: * @param pObjectName Given Object Name
125: *
126: * @return Hashtable with the back converted properties in it
127: * and will contain a "*" as key if the given object
128: * name is a property pattern for queries.
129: **/
130: public static Hashtable getProperties(ObjectName pObjectName) {
131: Hashtable lReturn = reverseProperties(pObjectName
132: .getKeyPropertyList());
133: if (pObjectName.isPropertyPattern()) {
134: lReturn.put("*", "*");
135: }
136: return lReturn;
137: }
138:
139: /**
140: * Takes the properties from the given Object Name and convert
141: * special characters back
142: *
143: * @param pObjectName Given Object Name
144: *
145: * @return String with the original Object Name String representation and
146: * when a property pattern Object Name for queries it contains a ",*"
147: * at the end.
148: **/
149: public static String getString(ObjectName pObjectName) {
150: String lReturn = pObjectName.getDomain() + ":"
151: + reverseString(pObjectName.getKeyPropertyList());
152: if (pObjectName.isPropertyPattern()) {
153: lReturn = lReturn + ",*";
154: }
155: return lReturn;
156: }
157:
158: /**
159: * Encrypt or decrypt the forbidden characters in an Object Name value property
160: *
161: * @param pValue Property Value of the Object Name's property list to be en- or decrypted
162: * @param pEncrypt True if the value must be encrypted otherwise decrypted
163: *
164: * @return A en- or decrypted String according to the conversion table above
165: **/
166: public static String convertCharacters(String pValue,
167: boolean pEncrypt) {
168: String lReturn = pValue;
169: if (pEncrypt) {
170: int lIndex = lReturn.indexOf("%");
171: while (lIndex >= 0) {
172: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
173: : "")
174: + "%25"
175: + ((lIndex + 1) < lReturn.length() ? lReturn
176: .substring(lIndex + 1) : "");
177: lIndex = lReturn.indexOf("%", lIndex + 2);
178: }
179: lIndex = lReturn.indexOf("*");
180: while (lIndex >= 0) {
181: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
182: : "")
183: + "%2a"
184: + ((lIndex + 1) < lReturn.length() ? lReturn
185: .substring(lIndex + 1) : "");
186: lIndex = lReturn.indexOf("*");
187: }
188: lIndex = lReturn.indexOf(":");
189: while (lIndex >= 0) {
190: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
191: : "")
192: + "%3a"
193: + ((lIndex + 1) < lReturn.length() ? lReturn
194: .substring(lIndex + 1) : "");
195: lIndex = lReturn.indexOf(":");
196: }
197: lIndex = lReturn.indexOf("?");
198: while (lIndex >= 0) {
199: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
200: : "")
201: + "%3f"
202: + ((lIndex + 1) < lReturn.length() ? lReturn
203: .substring(lIndex + 1) : "");
204: lIndex = lReturn.indexOf("?");
205: }
206: lIndex = lReturn.indexOf("=");
207: while (lIndex >= 0) {
208: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
209: : "")
210: + "%3d"
211: + ((lIndex + 1) < lReturn.length() ? lReturn
212: .substring(lIndex + 1) : "");
213: lIndex = lReturn.indexOf("=");
214: }
215: lIndex = lReturn.indexOf(",");
216: while (lIndex >= 0) {
217: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
218: : "")
219: + "%2c"
220: + ((lIndex + 1) < lReturn.length() ? lReturn
221: .substring(lIndex + 1) : "");
222: lIndex = lReturn.indexOf(",");
223: }
224: lIndex = lReturn.indexOf("'");
225: while (lIndex >= 0) {
226: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
227: : "")
228: + "%27"
229: + ((lIndex + 1) < lReturn.length() ? lReturn
230: .substring(lIndex + 1) : "");
231: lIndex = lReturn.indexOf("'");
232: }
233: lIndex = lReturn.indexOf("\"");
234: while (lIndex >= 0) {
235: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
236: : "")
237: + "%22"
238: + ((lIndex + 1) < lReturn.length() ? lReturn
239: .substring(lIndex + 1) : "");
240: lIndex = lReturn.indexOf("\"");
241: }
242: } else {
243: int lIndex = lReturn.indexOf("%2a");
244: while (lIndex >= 0) {
245: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
246: : "")
247: + "*"
248: + ((lIndex + 3) < lReturn.length() ? lReturn
249: .substring(lIndex + 3) : "");
250: lIndex = lReturn.indexOf("%2a");
251: }
252: lIndex = lReturn.indexOf("%3a");
253: while (lIndex >= 0) {
254: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
255: : "")
256: + ":"
257: + ((lIndex + 3) < lReturn.length() ? lReturn
258: .substring(lIndex + 3) : "");
259: lIndex = lReturn.indexOf("%3a");
260: }
261: lIndex = lReturn.indexOf("%3f");
262: while (lIndex >= 0) {
263: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
264: : "")
265: + "?"
266: + ((lIndex + 3) < lReturn.length() ? lReturn
267: .substring(lIndex + 3) : "");
268: lIndex = lReturn.indexOf("%3f");
269: }
270: lIndex = lReturn.indexOf("%3d");
271: while (lIndex >= 0) {
272: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
273: : "")
274: + "="
275: + ((lIndex + 3) < lReturn.length() ? lReturn
276: .substring(lIndex + 3) : "");
277: lIndex = lReturn.indexOf("%3d");
278: }
279: lIndex = lReturn.indexOf("%2c");
280: while (lIndex >= 0) {
281: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
282: : "")
283: + ","
284: + ((lIndex + 3) < lReturn.length() ? lReturn
285: .substring(lIndex + 3) : "");
286: lIndex = lReturn.indexOf("%2c");
287: }
288: lIndex = lReturn.indexOf("%25");
289: while (lIndex >= 0) {
290: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
291: : "")
292: + "%"
293: + ((lIndex + 3) < lReturn.length() ? lReturn
294: .substring(lIndex + 3) : "");
295: lIndex = lReturn.indexOf("%25");
296: }
297: lIndex = lReturn.indexOf("%27");
298: while (lIndex >= 0) {
299: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
300: : "")
301: + "'"
302: + ((lIndex + 3) < lReturn.length() ? lReturn
303: .substring(lIndex + 3) : "");
304: lIndex = lReturn.indexOf("%27");
305: }
306: lIndex = lReturn.indexOf("%22");
307: while (lIndex >= 0) {
308: lReturn = (lIndex > 0 ? lReturn.substring(0, lIndex)
309: : "")
310: + "\""
311: + ((lIndex + 3) < lReturn.length() ? lReturn
312: .substring(lIndex + 3) : "");
313: lIndex = lReturn.indexOf("%22");
314: }
315: }
316: return lReturn;
317: }
318:
319: /**
320: * takes the properties string and breaks it up into key/value pairs for
321: * insertion into a newly created hashtable.
322: *
323: * minimal validation is performed so that it doesn't blow up when
324: * constructing the kvp strings.
325: *
326: * checks for duplicate keys
327: *
328: * detects property patterns
329: *
330: */
331: private static ObjectName createObjectName(String domain,
332: String properties) throws MalformedObjectNameException {
333: if (null == properties || properties.length() < 1) {
334: throw new MalformedObjectNameException(
335: "null or empty properties");
336: }
337:
338: // The StringTokenizer below hides malformations such as ',,' in the
339: // properties string or ',' as the first or last character.
340: // Rather than asking for tokens and building a state machine I'll
341: // just manually check for those 3 scenarios.
342:
343: if (properties.startsWith(",") || properties.endsWith(",")
344: || properties.indexOf(",,") != -1) {
345: throw new MalformedObjectNameException(
346: "empty key/value pair in properties string");
347: }
348:
349: Hashtable ptable = new Hashtable();
350:
351: StringTokenizer tokenizer = new StringTokenizer(properties, ",");
352: boolean lPattern = false;
353: while (tokenizer.hasMoreTokens()) {
354: String chunk = tokenizer.nextToken();
355:
356: if (chunk.equals("*")) {
357: lPattern = true;
358: continue;
359: }
360:
361: int keylen = chunk.length();
362: int eqpos = chunk.indexOf('=');
363:
364: // test below: as in '=value' or 'key=' so that our substrings don't blow up
365: if (eqpos < 1 || (keylen == eqpos + 1)) {
366: throw new MalformedObjectNameException(
367: "malformed key/value pair: " + chunk);
368: }
369:
370: String key = chunk.substring(0, eqpos);
371: if (ptable.containsKey(key)) {
372: throw new MalformedObjectNameException(
373: "duplicate key: " + key);
374: }
375:
376: ptable.put(key, chunk.substring(eqpos + 1, keylen));
377: }
378:
379: return createObjectName(domain, ptable, lPattern);
380: }
381:
382: /**
383: * validates incoming properties hashtable
384: *
385: * builds canonical string
386: *
387: * precomputes the hashcode
388: */
389: private static ObjectName createObjectName(String domain,
390: Hashtable properties, boolean pPattern)
391: throws MalformedObjectNameException {
392: if (null == properties || (!pPattern && properties.size() < 1)) {
393: throw new MalformedObjectNameException(
394: "null or empty properties");
395: }
396:
397: Iterator it = properties.keySet().iterator();
398: Hashtable lReturn = new Hashtable(properties.size());
399: while (it.hasNext()) {
400: String key = null;
401: try {
402: key = (String) it.next();
403: } catch (ClassCastException e) {
404: throw new MalformedObjectNameException(
405: "key is not a string");
406: }
407:
408: String val = null;
409: try {
410: val = (String) properties.get(key);
411: } catch (ClassCastException e) {
412: throw new MalformedObjectNameException(
413: "value is not a string");
414: }
415:
416: // Search for invalid characters and replace them
417: String lKey = convertCharacters(key, true);
418: String lValue = convertCharacters(val, true);
419:
420: lReturn.put(lKey, lValue);
421: }
422: ObjectName result = new ObjectName(domain, lReturn);
423: if (pPattern)
424: return new ObjectName(result.getCanonicalName() + ",*");
425: return result;
426: }
427:
428: private static Hashtable reverseProperties(Hashtable pProperties) {
429: Hashtable lReturn = new Hashtable(pProperties.size());
430: Iterator i = pProperties.keySet().iterator();
431: while (i.hasNext()) {
432: String lKey = (String) i.next();
433: String lValue = (String) pProperties.get(lKey);
434: lKey = convertCharacters(lKey, false);
435: lValue = convertCharacters(lValue, false);
436: lReturn.put(lKey, lValue);
437: }
438: return lReturn;
439: }
440:
441: private static String reverseString(Hashtable pProperties) {
442: StringBuffer lReturn = new StringBuffer();
443: Iterator i = pProperties.keySet().iterator();
444: while (i.hasNext()) {
445: String lKey = (String) i.next();
446: String lValue = (String) pProperties.get(lKey);
447: lKey = convertCharacters(lKey, false);
448: lValue = convertCharacters(lValue, false);
449: if (lReturn.length() > 0) {
450: lReturn.append(",");
451: }
452: lReturn.append(lKey);
453: lReturn.append("=");
454: lReturn.append(lValue);
455: }
456: return lReturn.toString();
457: }
458: }
|