KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > system > pm > XMLAttributePersistenceManager


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.system.pm;
23
24 import java.beans.PropertyEditor JavaDoc;
25 import java.beans.PropertyEditorManager JavaDoc;
26 import java.io.ByteArrayInputStream JavaDoc;
27 import java.io.ByteArrayOutputStream JavaDoc;
28 import java.io.File JavaDoc;
29 import java.io.FileInputStream JavaDoc;
30 import java.io.FileOutputStream JavaDoc;
31 import java.io.FilenameFilter JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.io.InputStream JavaDoc;
34 import java.io.ObjectInputStream JavaDoc;
35 import java.io.ObjectOutputStream JavaDoc;
36 import java.io.OutputStream JavaDoc;
37 import java.io.Serializable JavaDoc;
38 import java.net.URL JavaDoc;
39 import java.text.SimpleDateFormat JavaDoc;
40 import java.util.Collections JavaDoc;
41 import java.util.Date JavaDoc;
42 import java.util.HashMap JavaDoc;
43 import java.util.Map JavaDoc;
44
45 import javax.management.Attribute JavaDoc;
46 import javax.management.AttributeList JavaDoc;
47 import javax.xml.parsers.DocumentBuilder JavaDoc;
48 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
49 import javax.xml.transform.OutputKeys JavaDoc;
50 import javax.xml.transform.Result JavaDoc;
51 import javax.xml.transform.Source JavaDoc;
52 import javax.xml.transform.Transformer JavaDoc;
53 import javax.xml.transform.TransformerFactory JavaDoc;
54 import javax.xml.transform.dom.DOMSource JavaDoc;
55 import javax.xml.transform.stream.StreamResult JavaDoc;
56
57 import org.jboss.logging.Logger;
58 import org.jboss.mx.persistence.AttributePersistenceManager;
59 import org.jboss.system.server.ServerConfigLocator;
60 import org.jboss.util.file.Files;
61 import org.w3c.dom.Comment JavaDoc;
62 import org.w3c.dom.Document JavaDoc;
63 import org.w3c.dom.Element JavaDoc;
64 import org.w3c.dom.Node JavaDoc;
65 import org.w3c.dom.NodeList JavaDoc;
66 import org.w3c.dom.Text JavaDoc;
67
68 /**
69  * XMLAttributePersistenceManager
70  *
71  * @author <a HREF="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
72  * @version $Revision: 57108 $
73 **/

74 public class XMLAttributePersistenceManager
75    implements AttributePersistenceManager
76 {
77    // Constants -----------------------------------------------------
78

79    /** The XML configuration element */
80    public static final String JavaDoc DATA_DIR_ELEMENT = "data-directory";
81    
82    /** Default base directory if one not specified */
83    public static final String JavaDoc DEFAULT_BASE_DIR = "data/xmbean-attrs";
84    
85    /** The XML attribute-list elements and attributes */
86    public static final String JavaDoc AL_ROOT_ELEMENT = "attribute-list";
87    public static final String JavaDoc AL_ID_ATTRIBUTE = "id";
88    public static final String JavaDoc AL_DATE_ATTRIBUTE = "date";
89    public static final String JavaDoc AL_ATTRIBUTE_ELEMENT = "attribute";
90    public static final String JavaDoc AL_NAME_ATTRIBUTE = "name";
91    public static final String JavaDoc AL_TYPE_ATTRIBUTE = "type";
92    public static final String JavaDoc AL_NULL_ATTRIBUTE = "null";
93    public static final String JavaDoc AL_SERIALIZED_ATTRIBUTE = "serialized";
94    public static final String JavaDoc AL_TRUE_VALUE = "true";
95    public static final String JavaDoc AL_FALSE_VALUE = "false";
96     
97    // Private Data --------------------------------------------------
98

99    /** Logger object */
100    private static final Logger log = Logger.getLogger(XMLAttributePersistenceManager.class);
101    
102    /** used for formating timestamps (date attribute) */
103    private static final SimpleDateFormat JavaDoc dateFormat = new SimpleDateFormat JavaDoc("yyyyMMddHHmmss");
104    
105    /** for byte-to-hex conversions */
106    private static final char[] hexDigits = new char[]
107       { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
108
109    /** directory as used internally */
110    private File JavaDoc dataDir;
111    
112    /** enable status */
113    private boolean state;
114    
115    /** id to filename cache */
116    private Map JavaDoc idMap;
117    
118    // Constructors -------------------------------------------------
119

120    /**
121     * Constructs a <tt>FileAttributePersistenceManager</tt>.
122    **/

123    public XMLAttributePersistenceManager()
124    {
125       log.debug("Constructed");
126    }
127    
128    // AttributePersistenceManager Lifecycle -------------------------
129

130    /**
131     * Initializes the AttributePersistenceManager using
132     * the supplied configuration element CONFIG_ELEMENT
133     * whose content will be probably different for each
134     * particular implementation.
135     *
136     * The version string is a tag that must be used by the
137     * AttributePersistenceManager implementation to make
138     * sure that data saved/loaded under different version
139     * tags are partitioned.
140     *
141     * Once created, the configuration of the implementation
142     * object cannot change.
143     *
144     * Calling any other method before create() is executed
145     * will result in a IllegalStateException
146     *
147     * Finally, the implementation should be prepared to
148     * receive multiple concurrent calls.
149     *
150     * @param version a tag to identify the version
151     * @param config XML Element to load arbitrary config
152     * @throws Exception when any error occurs during create
153     */

154    public void create(String JavaDoc version, Element JavaDoc config)
155       throws Exception JavaDoc
156    {
157       // ignore if already active
158
if (getState()) {
159          return;
160       }
161       
162       // Decide on the base data directory to use
163
String JavaDoc baseDir = null;
164       
165       if (config == null) {
166          baseDir = DEFAULT_BASE_DIR;
167       }
168       else {
169          if (!config.getTagName().equals(DATA_DIR_ELEMENT)) {
170             throw new Exception JavaDoc("expected '" + DATA_DIR_ELEMENT +
171                                 "' XML configuration element, got '" +
172                                 config.getTagName() + "'");
173          }
174          else {
175             baseDir = getElementContent(config);
176          }
177       }
178
179       // Initialize the data dir
180
this.dataDir = initDataDir(baseDir, version);
181       
182       log.debug("Using data directory: " + this.dataDir.getCanonicalPath());
183       
184       // initialize id cache
185
this.idMap = Collections.synchronizedMap(new HashMap JavaDoc());
186
187       // mark active status
188
setState(true);
189    }
190
191    /**
192     * Returns true if the AttributePersistenceManager
193     * is "in-service" state, i.e. after create() and
194     * before destroy() has been called, false otherwise.
195     *
196     * @return true if in operational state
197     */

198    public boolean getState()
199    {
200       return this.state;
201    }
202    
203    /**
204     * Releases resources and destroys the AttributePersistenceManager.
205     * The object is unusable after destroy() has been called.
206     *
207     * Any call to any method will result to an
208     * IllegalStateException.
209     *
210     */

211    public void destroy()
212    {
213       setState(false);
214
215       // instance can't be use anymore
216
this.dataDir = null;
217       this.idMap = null;
218    }
219    
220    // AttributePersistenceManager Persistence -----------------------
221

222    /**
223     * Uses the specified id to retrieve a previously persisted
224     * AttributeList. If no data can be found under the specified
225     * id, a null will be returned.
226     *
227     * @param id the key for retrieving the data
228     * @return the data, or null
229     * @throws Exception when an error occurs
230     */

231    public void store(String JavaDoc id, AttributeList JavaDoc attrs)
232       throws Exception JavaDoc
233    {
234       log.debug("store(" + id + ") attrs=" + attrs);
235
236       // make sure we are active
237
checkActiveState();
238       
239       // map to filename - keep the original for storing
240
String JavaDoc origId = id;
241       id = mapId(id);
242       
243       if (attrs == null)
244          throw new Exception JavaDoc("store() called with null AttributeList");
245       
246       // will throw an exception if file not r/w or it is a directory
247
File JavaDoc file = checkFileForWrite(id);
248       
249       // build the XML in memory using DOM
250
DocumentBuilder JavaDoc builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
251       Document JavaDoc doc = builder.newDocument();
252
253       // Add a comment
254
Comment JavaDoc comment = doc.createComment(" automatically produced by XMLAttributePersistenceManager ");
255       doc.appendChild(comment);
256       
257       // Insert root element
258
Element JavaDoc root = doc.createElement(AL_ROOT_ELEMENT);
259       root.setAttribute(AL_ID_ATTRIBUTE, origId);
260       root.setAttribute(AL_DATE_ATTRIBUTE, dateFormat.format(new Date JavaDoc()));
261       doc.appendChild(root);
262       
263       // iterate over the AttributeList
264
for (int i = 0; i < attrs.size(); i++) {
265          
266          Attribute JavaDoc attr = (Attribute JavaDoc)attrs.get(i);
267        
268          String JavaDoc name = attr.getName();
269          Object JavaDoc value = attr.getValue();
270          
271          // create the Element and decide how to fill it in
272
Element JavaDoc element = doc.createElement(AL_ATTRIBUTE_ELEMENT);
273          element.setAttribute(AL_NAME_ATTRIBUTE, name);
274          
275          if (value == null) {
276             // (a) null value - mark it as null
277
element.setAttribute(AL_NULL_ATTRIBUTE, AL_TRUE_VALUE);
278             
279             // append the attribute to the attribute-list
280
root.appendChild(element);
281          }
282          else if (value instanceof org.w3c.dom.Element JavaDoc) {
283             // (b) XML Element - mark the type and append a copy of it
284
element.setAttribute(AL_TYPE_ATTRIBUTE, "org.w3c.dom.Element");
285             
286             Node JavaDoc copy = doc.importNode((org.w3c.dom.Element JavaDoc)value, true);
287             element.appendChild(copy);
288             
289             // append the attribute to the attribute-list
290
root.appendChild(element);
291          }
292          else {
293             Class JavaDoc clazz = value.getClass();
294             String JavaDoc type = clazz.getName();
295             PropertyEditor JavaDoc peditor = PropertyEditorManager.findEditor(clazz);
296
297             if (peditor != null) {
298                // (c) use a PropertyEditor - mark the type and append the value as string
299
peditor.setValue(value);
300                
301                element.setAttribute(AL_TYPE_ATTRIBUTE, type);
302                element.appendChild(doc.createTextNode(peditor.getAsText()));
303                
304                // append the attribute to the attribute-list
305
root.appendChild(element);
306             }
307             else if (value instanceof Serializable JavaDoc) {
308                // (d) serialize the object - mark type and serialized attribute
309
// - encode the value as stringfied sequence of hex
310
String JavaDoc encoded = encodeAsHexString((Serializable JavaDoc)value);
311                
312                if (encoded != null) {
313                   element.setAttribute(AL_TYPE_ATTRIBUTE, type);
314                   element.setAttribute(AL_SERIALIZED_ATTRIBUTE, AL_TRUE_VALUE);
315                   element.appendChild(doc.createTextNode(encoded));
316
317                   // append the attribute to the attribute-list
318
root.appendChild(element);
319                }
320                else {
321                   // could not serialize the object - write and log a warning
322
root.appendChild(doc.createComment(
323                         " WARN <attribute name=\"" + name + "\" type=\"" + type +
324                         "\"/> could not be serialized "));
325                   
326                   log.warn("Could not serialize attribute '" + name +
327                            "' of type '" + type + "' and value: " + value);
328                }
329             }
330             else {
331                // (e) could not find a way to persist - record and log a warning
332
root.appendChild(doc.createComment(
333                      " WARN <attribute name=\"" + name + "\" type=\"" + type +
334                      "\"/> could not be persisted "));
335                
336                log.warn("Could not find a way to persist attribute '" + name +
337                         "' of type '" + type + "' and value: " + value);
338             }
339          }
340       }
341       
342       // DOM document ready - save it
343
try {
344          outputXmlFile(doc, file);
345       }
346       catch (Exception JavaDoc e) {
347          log.warn("Cannot persist AttributeList to: \"" + id + "\"", e);
348          throw e;
349       }
350    }
351    
352    /**
353     * Persists an AttributeList (name/value pair list),
354     * under a specified id. The id can be used to retrieve the
355     * AttributeList later on. The actual mechanism will differ
356     * among implementations.
357     *
358     * @param id the key for retrieving the data later on, not null
359     * @param attrs the data to be persisted, not null
360     * @throws Exception when data cannot be persisted
361     */

362    public AttributeList JavaDoc load(String JavaDoc id)
363       throws Exception JavaDoc
364    {
365       log.debug("load(" + id + ")");
366
367       // make sure we are active
368
checkActiveState();
369       
370       // map to filename
371
id = mapId(id);
372       
373       if (!getState())
374          return null;
375       
376       // Real stuff starts here
377
AttributeList JavaDoc attrs = null;
378       
379       // returns null to indicate file does not exist
380
File JavaDoc file = checkFileForRead(id);
381       
382       if (file != null) {
383          // parse the saved XML doc
384
Document JavaDoc doc = parseXmlFile(file);
385          
386          // top level - look for AL_ROOT_ELEMENT
387
NodeList JavaDoc docList = doc.getChildNodes();
388          Element JavaDoc root = null;
389          
390          for (int i = 0; i < docList.getLength(); i++) {
391             Node JavaDoc node = docList.item(i);
392             
393             if (node.getNodeType() == Node.ELEMENT_NODE &&
394                 node.getNodeName().equals(AL_ROOT_ELEMENT)) {
395                
396                root = (Element JavaDoc)node;
397                break; // found
398
}
399          }
400          
401          // root element must be there
402
if (root == null) {
403             throw new Exception JavaDoc("Expected XML element: " + AL_ROOT_ELEMENT);
404          }
405          else {
406             // proceed iterating over AL_ATTRIBUTE_ELEMENT elements
407
// and fill the AttributeList
408
attrs = new AttributeList JavaDoc();
409             
410             NodeList JavaDoc rootList = root.getChildNodes();
411             
412             for (int i = 0; i < rootList.getLength(); i++) {
413                Node JavaDoc node = rootList.item(i);
414                
415                // only interested in ELEMENT nodes
416
if (node.getNodeType() == Node.ELEMENT_NODE &&
417                    node.getNodeName().equals(AL_ATTRIBUTE_ELEMENT)) {
418                   
419                   Element JavaDoc element = (Element JavaDoc)node;
420                   
421                   // name attribute must always be there
422
String JavaDoc name = element.getAttribute(AL_NAME_ATTRIBUTE);
423                   
424                   if (!(name.length() > 0)) {
425                      throw new Exception JavaDoc("Attribute '" + AL_NAME_ATTRIBUTE +
426                                          "' must be specified for element '" + AL_ATTRIBUTE_ELEMENT + "'");
427                   }
428                   
429                   // Process the attribute depending on how the attributes are set
430

431                   if (element.getAttribute(AL_NULL_ATTRIBUTE).toLowerCase().equals(AL_TRUE_VALUE)) {
432                      
433                      // (a) null value - just add it to the AttributeList
434
attrs.add(new Attribute JavaDoc(name, null));
435                   }
436                   else if (element.getAttribute(AL_SERIALIZED_ATTRIBUTE).toLowerCase().equals(AL_TRUE_VALUE)) {
437                      
438                      // (b) serialized value - decode the HexString
439
String JavaDoc hexStr = getElementContent(element);
440                      Serializable JavaDoc obj = decodeFromHexString(hexStr);
441                      
442                      if (obj == null) {
443                         throw new Exception JavaDoc("Failed to deserialize attribute '" + name + "'");
444                      }
445                      else {
446                         attrs.add(new Attribute JavaDoc(name, obj));
447                      }
448                   }
449                   else {
450                      String JavaDoc type = element.getAttribute(AL_TYPE_ATTRIBUTE);
451                      
452                      // type must be specified
453
if (!(type.length() > 0)) {
454                         throw new Exception JavaDoc("Attribute '" + AL_TYPE_ATTRIBUTE +
455                                             "' must be specified for name='" + name + "'");
456                      }
457                      
458                      if (type.equals("org.w3c.dom.Element")) {
459
460                         // (c) org.w3c.dom.Element - deep copy first Element child node found
461

462                          NodeList JavaDoc nlist = element.getChildNodes();
463                          Element JavaDoc el = null;
464                          
465                          for (int j = 0; j < nlist.getLength(); j++) {
466                             
467                             Node JavaDoc n = nlist.item(j);
468                             if (n.getNodeType() == Node.ELEMENT_NODE)
469                             {
470                                el = (Element JavaDoc)n;
471                                break;
472                             }
473                          }
474                          
475                          if (el != null) {
476                             attrs.add(new Attribute JavaDoc(name, el.cloneNode(true)));
477                          }
478                          else {
479                             attrs.add(new Attribute JavaDoc(name, null));
480                          }
481                      }
482                      else {
483                         // Get the classloader for loading attribute classes.
484
ClassLoader JavaDoc cl = Thread.currentThread().getContextClassLoader();
485                         Class JavaDoc clazz = null;
486                         
487                          try {
488                             clazz = cl.loadClass(type);
489                          }
490                          catch (ClassNotFoundException JavaDoc e) {
491                             throw new Exception JavaDoc("Class not found for attribute '" + name +
492                                                 "' of type '" + type + "'");
493                          }
494
495                          PropertyEditor JavaDoc peditor = PropertyEditorManager.findEditor(clazz);
496
497                          if (peditor != null) {
498                             
499                             // (d) use a PropertyEditor - extract the value
500

501                             String JavaDoc value = getElementContent(element);
502                             peditor.setAsText(value);
503                             
504                             attrs.add(new Attribute JavaDoc(name, peditor.getValue()));
505                          }
506                          else {
507                             throw new Exception JavaDoc("Cannot find a way to load attribute '" + name +
508                                                 "' of type '" + type + "'");
509                          }
510                      }
511                   }
512                }
513             } // end for()
514
}
515       }
516       log.debug("load() returns with: " + attrs);
517       
518       // will be null if a persistent file was not found
519
return attrs;
520    }
521
522    // Administrative Functions --------------------------------------
523

524    /**
525     * Checks if a persistened AttributeList for this particular
526     * id exists
527     *
528     * @param id the key of the image
529     * @return true if an image exists; false otherwise
530     * @throws Exception on any error
531     */

532    public boolean exists(String JavaDoc id)
533       throws Exception JavaDoc
534    {
535       // make sure we are active
536
checkActiveState();
537       
538       return (new File JavaDoc(this.dataDir, mapId(id))).isFile();
539    }
540    
541    /**
542     * Removes the persisted AttributeList, if exists
543     *
544     * @param id the key of the image
545     * @throws Exception when any error occurs
546     */

547    public void remove(String JavaDoc id)
548       throws Exception JavaDoc
549    {
550       // make sure we are active
551
checkActiveState();
552       
553       (new File JavaDoc(this.dataDir, mapId(id))).delete();
554    }
555    
556    /**
557     * Removes all the persisted data stored under
558     * the configured version tag.
559     *
560     * @throws Exception when any error occurs
561     */

562    public void removeAll()
563       throws Exception JavaDoc
564    {
565       // make sure we are active
566
checkActiveState();
567       
568       String JavaDoc[] files = this.dataDir.list(new XMLFilter());
569       
570       if (files != null) {
571          for (int i = 0; i < files.length; i++) {
572             (new File JavaDoc(this.dataDir, files[i])).delete();
573          }
574       }
575    }
576    
577    /**
578     * Returns a String array with all the saved ids
579     * under the configured version tag.
580     *
581     * @return array with all persisted ids
582     * @throws Exception when any error occurs
583     */

584    public String JavaDoc[] listAll()
585       throws Exception JavaDoc
586    {
587       // make sure we are active
588
checkActiveState();
589       
590       String JavaDoc[] files = this.dataDir.list(new XMLFilter());
591       String JavaDoc[] result = null;
592       
593       if (files != null) {
594          result = new String JavaDoc[files.length];
595          
596          for (int i = 0; i < files.length; i++) {
597             result[i] = mapFile(files[i]);
598          }
599       }
600       return result;
601    }
602    
603    // Private -------------------------------------------------------
604

605    /**
606     * Private status setter
607     *
608     * lifecycle is controlled by create() and destroy()
609     */

610    private void setState(boolean state)
611    {
612       this.state = state;
613    }
614    
615    /**
616     * Create/check/point to the data directory.
617     *
618     * Process the base dir first and append the versionTag dir
619     * if specified.
620     *
621     * Only a base dir relative to ServerHomeDir is created.
622     * External base dirs are only used (i.e. they must exist
623     * and we must read/write permission)
624     */

625    private File JavaDoc initDataDir(String JavaDoc baseDir, String JavaDoc versionTag)
626       throws Exception JavaDoc
627    {
628       File JavaDoc dir = null;
629       
630       // Process the base directory first
631

632       // baseDir must be valid URL pointing to r/w dir
633
try {
634          URL JavaDoc fileURL = new URL JavaDoc(baseDir);
635           
636          File JavaDoc file = new File JavaDoc(fileURL.getFile());
637           
638          if(file.isDirectory() && file.canRead() && file.canWrite()) {
639             dir = file;
640          }
641       }
642       catch(Exception JavaDoc e) {
643          // Otherwise, try to make it inside the jboss directory hierarchy
644

645          File JavaDoc homeDir = ServerConfigLocator.locate().getServerHomeDir();
646     
647          dir = new File JavaDoc(homeDir, baseDir);
648     
649          dir.mkdirs();
650           
651          if (!dir.isDirectory())
652             throw new Exception JavaDoc("The base directory is not valid: "
653                                 + dir.getCanonicalPath());
654       }
655       
656       // Now add the versionTag dir, if specified
657
if (versionTag != null && !versionTag.equals("")) {
658          dir = new File JavaDoc(dir, versionTag);
659          
660          dir.mkdirs();
661           
662          if (!dir.isDirectory())
663             throw new Exception JavaDoc("The data directory is not valid: "
664                                 + dir.getCanonicalPath());
665       }
666       return dir;
667    }
668    
669    /**
670     * Serialize an object as a Hex string so it can be saved as text
671     *
672     * The length of the encoded object is twice it's byte image
673     *
674     * Returns null if serialization fails
675     */

676    private String JavaDoc encodeAsHexString(Serializable JavaDoc obj)
677    {
678       String JavaDoc retn = null;
679       
680       if (obj != null) {
681          try {
682             ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc(1024);
683             ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(baos);
684           
685             oos.writeObject(obj);
686             byte[] bytes = baos.toByteArray();
687             
688             StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc(1024);
689
690             for (int i = 0; i < bytes.length; i++) {
691                 sbuf.append(hexDigits[ (bytes[i] >> 4) & 0xF ]); // high order digit
692
sbuf.append(hexDigits[ (bytes[i] ) & 0xF ]); // low order digit
693
}
694
695             retn = sbuf.toString();
696          }
697          catch (IOException JavaDoc e) {
698             // will return null
699
}
700       }
701       return retn;
702    }
703
704    /**
705     * Deserialize an object from its hex encoded string representation
706     *
707     * Returns null if deserialization fails
708     */

709    private Serializable JavaDoc decodeFromHexString(String JavaDoc hexStr)
710    {
711       // hexStr must not contain white space!
712
int len = hexStr.length() / 2;
713       byte[] bytes = new byte[len];
714       
715       for (int i = 0; i < len; i++) {
716          
717           char h1 = hexStr.charAt(i * 2); // high order hex digit
718
char h2 = hexStr.charAt(i * 2 + 1); // low order hex digit
719

720           // convert hex digits to integers
721
int d1 = (h1 >= 'a') ? (10 + h1 - 'a')
722                 : ((h1 >= 'A') ? (10 + h1 - 'A')
723                                    : (h1 - '0'));
724           
725           int d2 = (h2 >= 'a') ? (10 + h2 - 'a')
726                 : ((h2 >= 'A') ? (10 + h2 - 'A')
727                                     : (h2 - '0'));
728           
729           bytes[i] = (byte)(d1 * 16 + d2); // 255 max
730
}
731       
732       Serializable JavaDoc retn = null;
733       
734       try {
735