001    package edu.nrao.sss.util;
002    
003    import java.io.FileNotFoundException;
004    import java.io.FileReader;
005    import java.io.InputStream;
006    import java.io.Reader;
007    import java.io.StringWriter;
008    import java.io.Writer;
009    import java.net.MalformedURLException;
010    import java.net.URL;
011    import java.util.Properties;
012    
013    import javax.xml.XMLConstants;
014    import javax.xml.bind.JAXBContext;
015    import javax.xml.bind.JAXBException;
016    import javax.xml.bind.Marshaller;
017    import javax.xml.bind.PropertyException;
018    import javax.xml.bind.Unmarshaller;
019    import javax.xml.stream.XMLInputFactory;
020    import javax.xml.stream.XMLStreamException;
021    import javax.xml.stream.XMLStreamReader;
022    import javax.xml.validation.Schema;
023    import javax.xml.validation.SchemaFactory;
024    
025    import org.apache.log4j.Logger;
026    import org.xml.sax.SAXException;
027    
028    /**
029     * A utility for converting an object to XML and vice versa.
030     * <p>
031     * This class will be used primarily in the {@code toXml} and {@code fromXml}
032     * methods of other classes.</p> 
033     * <p>
034     * <b><u>How Schema Files are Located and Used</u></b></p>
035     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
036     * If a transformation method (one that turns XML into an object or vice versa)
037     * takes a {@code Schema} parameter, that schema will be the one used for
038     * validation.  If, however, the {@code Schema} parameter is <i>null</i>,
039     * this utility will either attempt to find a default schema or bypass
040     * schema validation, depending on the state of the {@code lookForDefaultSchema}
041     * property of this utility.
042     * </p>
043     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
044     * The location of the default schema files is given by this utility's
045     * {@code schemaBaseUrl} property.  If this property has been set to
046     * <i>null</i>, then the default location for the schema files will be
047     * the location on the classpath of the targeted class.
048     * </p>
049     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
050     * The name of the primary default schema file, regardless of its
051     * default location, is <tt>theClass.getSimpleName() + ".xsd"</tt>.
052     * If a primary default schema file cannot be found, a secondary default
053     * is used.  The name of the secondary default file is <tt>packageName.xsd</tt>
054     * where <tt>packageName</tt> is the "simple" package name.  For example, the
055     * simple package name of edu.nrao.sss.model.source is "source", thus the
056     * schema file would be "source.xsd".
057     * </p>
058     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
059     * <em>New 2008-12-30:</em>
060     * If, in the example above, "source.xsd" is not found, an attempt will
061     * be made to find "model.xsd", "sss.xsd", and so on up the line.
062     * If none of those files exist a log entry will be created and a
063     * <i>null</i> schema will be used.  Depending no the particular usage
064     * this could result in an exception or marshalling / unmarshalling
065     * without schema verification.
066     * </p>
067     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
068     * Note that a default schema file
069     * is sought <i>only</i> when the {@code lookForDefaultSchema} property
070     * is <i>true</i>.  If we seek a default schema file but fail to find it,
071     * what happens next depends on the value of the
072     * {@code failIfDefaultSchemaNotFound} property.  If that property is
073     * <i>false</i>, transformation occurs without validation.  If it is
074     * <i>true</i>, the transformation methods will throw {@code JaxbException}s.
075     * </p>
076     * <p style="margin-left:1em; margin-right:5em; text-align:justify">
077     * At this time the {@code schemaBaseUrl} can be set only 
078     * {@link #setBaseUrlForSchemaFiles(URL) programatically}.  A possible future
079     * enhancement would allow the specification of this value via a
080     * properties file.</p>
081     * <p>
082     * <b>Version Info:</b>
083     * <table style="margin-left:2em">
084     *   <tr><td>$Revision: 2276 $</td></tr>
085     *   <tr><td>$Date: 2009-04-29 10:34:57 -0600 (Wed, 29 Apr 2009) $</td></tr>
086     *   <tr><td>$Author: dharland $</td></tr>
087     * </table></p>
088     *  
089     * @author David M. Harland
090     * @since 2006-09-05
091     */
092    public class JaxbUtility
093    {
094      private static final Logger log = Logger.getLogger(JaxbUtility.class);
095      
096      /** Returns a shared, pre-constructed, instance of this utility. */
097      public static JaxbUtility getSharedInstance()  { return sharedInstance; }
098      
099      //============================================================================
100      // CONFIGURING THIS UTILITY
101      //============================================================================
102      
103      private static final String DEFAULT_BASE_URL;
104    
105      //Set the default location for the schemas via a properties file
106      static
107      {
108        String url;
109        try
110        {
111          Properties props = new Properties();
112          
113          InputStream file =
114            JaxbUtility.class.getResourceAsStream("jaxbUtility.properties");
115          
116          props.load(file);
117          
118          url = props.getProperty("schemaLocation", "https://e2e.nrao.edu/schemas/sss/");
119        }
120        catch (Exception ex)
121        {
122          url = "https://e2e.nrao.edu/schemas/sss/";
123        }
124        DEFAULT_BASE_URL = url;
125      }
126      
127      private static final JaxbUtility sharedInstance = new JaxbUtility();
128    
129      private SchemaFactory schemaFactory;
130      private URL           schemaBaseUrl;
131      private boolean       lookForDefaultSchema;
132      private boolean       failIfDefaultSchemaNotFound;
133      
134      /** Creates a new instance. */
135      public JaxbUtility()
136      {
137        lookForDefaultSchema        = true;
138        failIfDefaultSchemaNotFound = true;
139        
140        schemaFactory =
141          SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
142    
143        try {
144          schemaBaseUrl = new URL(DEFAULT_BASE_URL);
145        }
146        catch (MalformedURLException ex) {
147          schemaBaseUrl = null;
148        }
149    }
150    
151      //============================================================================
152      // SCHEMA
153      //============================================================================
154      
155      /**
156       * Tells this utility whether or not it should look for a default schema
157       * if one is not specified.  A schema is not specified if a method that
158       * takes no schema argument is called, or if a method with a schema
159       * argument is called, but the passed, or resulting, schema is <i>null</i>.
160       * <p>
161       * The default value of this property is <i>true</i>.</p>
162       * <p>
163       * See the note, <i>How Schema Files are Located and Used</i>, at the
164       * {@link JaxbUtility top} of this class for more information about
165       * schema acquisition and usage.</p>
166       * 
167       * @param look <i>true</i> if this utility should seek out a default schema.
168       */
169      public void setLookForDefaultSchema(boolean look)
170      {
171        lookForDefaultSchema = look;
172      }
173    
174      /**
175       * Tells this utility whether or not failure to find a default schema
176       * should result in failure of the transformation methods.  If this
177       * property is <i>true</i>, those methods will fail with
178       * {@code JaxbException}s.  If this property is <i>false</i>, those
179       * methods will perform transformations without validation.
180       * <p>
181       * The default value of this property is <i>true</i>.</p>
182       *  
183       * @param fail signals the transformations to fail if a default schema
184       *             is sought but not found.
185       */
186      public void setFailIfDefaultSchemaNotFound(boolean fail)
187      {
188        failIfDefaultSchemaNotFound = fail;
189      }
190      
191      /**
192       * Sets the base URL that this utility will use for locating schema files.
193       * To configure this utility so that the default schema files are found
194       * on the classpath, use a parameter of <i>null</i>.
195       * <p>
196       * The default value of this property is
197       * <a href="https://e2e.nrao.edu/schemas/">
198       * https://e2e.nrao.edu/schemas/</a>.</p>
199       * <p>
200       * See the note, <i>How Schema Files are Located and Used</i>, at the
201       * {@link JaxbUtility top} of this class for more information about
202       * schema acquisition and usage.</p>
203       * 
204       * @param baseUrl the base URL that this utility will use for locating schema
205       *                files.  A value of <i>null</i> may be used to tell this
206       *                utility to look for default schema files on the classpath.
207       */
208      public void setBaseUrlForSchemaFiles(URL baseUrl)
209      {
210        schemaBaseUrl = baseUrl;
211      }
212      
213      /**
214       * Returns the base URL that this utility will use for locating schema files.
215       * <p>
216       * See the note, <i>How Schema Files are Located and Used</i>, at the
217       * {@link JaxbUtility top} of this class for more information about
218       * schema acquisition and usage.</p>
219       * 
220       * @return the base URL that this utility will use for locating schema files.
221       */
222      public URL getBaseUrlForSchemaFiles()
223      {
224        return schemaBaseUrl;
225      }
226      
227      /**
228       * Attempts to build and return a {@code Schema} to use for objects of the
229       * given class.  This method does not look at the value of the
230       * {@link #setLookForDefaultSchema(boolean) lookForDefaultSchema}
231       * property.
232       * 
233       * @param c the class for which a schema is desired.
234       * 
235       * @return a schema to use for objects of type {@code c}, or <i>null</i>
236       *         if no such schema could be found.
237       *         
238       * @throws JAXBException if a schema file was found for {@code c} but
239       *           something went wrong in the process of turning the file into
240       *           a {@code Schema} object.
241       *           
242       * @see #getSchema(String, Class)
243       */
244      public Schema getSchemaFor(Class<?> c) throws JAXBException
245      {
246        //Quick exit for null class
247        if (c == null)
248          return null;
249    
250        Schema schema   = null;
251        String fileName = c.getSimpleName() + ".xsd";
252        
253        //Look for schemaBaseUrl/className.xsd 
254        try
255        {
256          schema = getSchema(fileName, c);
257        }
258        //If we couldn't find the file, look for schemaBaseUrl/packageName.xsd
259        catch (FileNotFoundException ex1)
260        {
261          schema = getSchemaFor(c.getPackage().getName(), c);
262        }
263        //If we did find the file, but it could not be made into a schema, throw
264        catch (Exception ex2)
265        {
266          throw new JAXBException(ex2);
267        }
268        
269        //This will be null if no schema was found
270        return schema;
271      }
272      
273      private Schema getSchemaFor(String packageName, Class<?> c) throws JAXBException
274      {
275        Schema schema = null;
276        
277        String[] pkgComponent = packageName.split("\\.");
278        
279        //If pkg name is x.y.z, first try z.xsd, then y.xsd, then x.xsd.
280        //Stop at first file found.  If none found, log finding and return null.
281        for (int i=pkgComponent.length-1; i >= 0; i--)
282        {
283          String fileName = pkgComponent[i] + ".xsd";
284          try
285          {
286            schema = getSchema(fileName, c);
287            break; //success
288          }
289          //If package schema not found, result will be null
290          catch (FileNotFoundException exA)
291          {
292            if (i == 0)
293              log.warn("Could find no schema for class " + c.getName());
294          }
295          //If pkg schema was found but could not be made into schema, throw
296          catch (Exception exB)
297          {
298            throw new JAXBException(exB);
299          }
300        }
301        
302        return schema;
303      }
304      
305      /**
306       * Attempts to build and return a {@code Schema} based on the given
307       * URL to use for objects of the given class.  The class <tt>c</tt>
308       * is used only if the {@link #setBaseUrlForSchemaFiles(URL)
309       * base URL} used by this utility is <i>null</i>.
310       * 
311       * @param relativeUrl typically the name of the schema file
312       * 
313       * @param c the class used to locate the schema file, but only in
314       *          the event that the {@link #setBaseUrlForSchemaFiles(URL)
315       *          base URL} used by this utility is <i>null</i>.
316       *          
317       * @return a schema, or <i>null</i> if one could not be created.
318       * 
319       * @throws FileNotFoundException if URL can't be found.
320       * 
321       * @throws SAXException if URL found but can't be turned into schema.
322       */
323      public Schema getSchema(String relativeUrl, Class<?> c)
324        throws FileNotFoundException, SAXException
325      {
326        URL schemaUrl = null;
327    
328        //See if we can find a schema file at the URL
329        try
330        {
331          schemaUrl =
332            (schemaBaseUrl == null) ? c.getResource(relativeUrl)
333                                    : new URL(schemaBaseUrl, relativeUrl);
334          log.debug("Looking for XML schema: " + schemaUrl.toString());
335          schemaUrl.openConnection().getContent();
336        }
337        //If not, report file-not-found
338        catch (Exception ex1)
339        {
340          throw new FileNotFoundException("Could not find schema file " +
341                                          (schemaBaseUrl == null ?
342                                           "CLASSPATH:" :
343                                           schemaBaseUrl.toExternalForm()
344                                          ) + relativeUrl);
345        }
346        
347        //Return schema or throw parsing exception
348        log.debug("  Reading XML schema: " + (schemaUrl==null? "NULL" : schemaUrl.toString()));
349        
350        return schemaFactory.newSchema(schemaUrl);
351      }
352      
353      //============================================================================
354      // MAIN METHODS
355      //============================================================================
356      
357      /**
358       * Writes {@code o} as XML to {@code writer}, using {@code schema} for
359       * validation.
360       * <p>
361       * If {@code schema} is non-null, it is used to validate the contents
362       * of this object while converting it to XML.
363       * If {@code schema} is <i>null</i>, one of two approaches will be
364       * taken, depending on the state of the {@code lookForDefaultSchema}
365       * property:
366       * <ol>
367       *   <li>If <i>false</i>, the object {@code o} will be transformed into XML
368       *       without validation against a schema.</li>
369       *   <li>If <i>true</i>, this utility will attempt to locate a default
370       *       schema (see {@link #setLookForDefaultSchema(boolean)}.  If found, it
371       *       will be used to validate the object {@code o} during the
372       *       transformation to XML.  If not found, the object {@code o} will be
373       *       transformed into XML without validation against a schema.</li>
374       * </ol></p>
375       * <p>
376       * If there are any problems encountered during the transformation to XML,
377       * a {@code JAXBException} will be thrown.</p>
378       *
379       * @param writer the device to which XML is written.
380       *               If this value is <i>null</i>, no action is taken.
381       *               
382       * @param o the object to be expressed in XML.
383       *          If this value is <i>null</i>, no action is taken.
384       *          
385       * @param schema the holder of the rules for validating the XML output.
386       *               If this value is <i>null</i>, and if
387       *               {@link #setLookForDefaultSchema(boolean)} was called
388       *               most recently with a value of <i>true</i>, an attempt
389       *               will be made to locate a default schema.
390       *               
391       * @throws JAXBException if any problems are encountered during the
392       *           transformation to XML.
393       */
394      public void writeObjectAsXmlTo(Writer writer, Object o, Schema schema)
395        throws JAXBException
396      {
397        if ((writer == null) || (o == null))
398          return;
399        
400        log.debug("Write XML for class " + o.getClass().getName() + " to writer.");
401    
402        JAXBContext jaxb = JAXBContext.newInstance(o.getClass());
403        
404        if ((schema == null) && lookForDefaultSchema)
405        {
406          schema = getSchemaFor(o.getClass());
407          
408          if ((schema == null) && failIfDefaultSchemaNotFound)
409            throw new JAXBException(schemaNotFoundMessage(o.getClass()));
410        }
411        
412        Marshaller xmlConverter = createAndConfigureMarshaller(jaxb, schema);
413        
414        xmlConverter.marshal(o, writer);
415      }
416      
417      /**
418       * Creates and returns a new object based on the data read from an XML source.
419       * <p>
420       * See {@link #writeObjectAsXmlTo(Writer, Object, Schema)} to understand how
421       * the {@code schema} parameter is treated.</p>
422       * 
423       * @param <T> the runtime type of the returned object.
424       * 
425       * @param reader the source of the XML data.
426       *               If this value is <i>null</i>, <i>null</i> is returned.
427       *               
428       * @param c the class of which the returned object is an instance. 
429       *          If this value is <i>null</i>, <i>null</i> is returned.
430       *          
431       * @param schema the schema used to verify the XML file.
432       * 
433       * @return an object based on the data in an XML file.
434       * 
435       * @throws XMLStreamException if the XML is not well-formed,
436       *           or for some other "unexpected processing conditions".
437       *           
438       * @throws JAXBException if anything else goes wrong during the
439       *           transformation.
440       */
441      public <T> T readObjectAsXmlFrom(Reader reader, Class<T> c, Schema schema)
442        throws JAXBException, XMLStreamException
443      {
444        if ((reader == null) || (c == null))
445          return null;
446        
447        log.debug("Read XML for class " + c.getName() + " from reader.");
448    
449        JAXBContext jaxb = JAXBContext.newInstance(c);
450        
451        if ((schema == null) && lookForDefaultSchema)
452        {
453          schema = getSchemaFor(c);
454          
455          if ((schema == null) && failIfDefaultSchemaNotFound)
456            throw new JAXBException(schemaNotFoundMessage(c));
457        }
458        
459        Unmarshaller xmlInterpreter = createAndConfigureUnmarshaller(jaxb, schema);
460        
461        XMLStreamReader xmlReader =
462          XMLInputFactory.newInstance().createXMLStreamReader(reader);
463    
464        return xmlInterpreter.unmarshal(xmlReader, c).getValue();
465      }
466    
467      /**
468       * Creates and returns a new object based on the data read from an XML source.
469       * <p>
470       * See {@link #writeObjectAsXmlTo(Writer, Object, Schema)} to understand how
471       * the {@code schema} parameter is treated.</p>
472       * 
473       * @param <T> the runtime type of the returned object.
474       * 
475       * @param stream the source of the XML data.
476       *               If this value is <i>null</i>, <i>null</i> is returned.
477       * 
478       * @param c the class of which the returned object is an instance. 
479       *          If this value is <i>null</i>, <i>null</i> is returned.
480       *          
481       * @param schema the schema used to verify the XML file.
482       * 
483       * @return an object based on the data in an XML file.
484       * 
485       * @throws XMLStreamException if the XML is not well-formed,
486       *           or for some other "unexpected processing conditions".
487       *           
488       * @throws JAXBException if anything else goes wrong during the
489       *           transformation.
490       */
491      public <T> T readObjectAsXmlFrom(InputStream stream,
492                                       Class<T> c, Schema schema)
493        throws JAXBException, XMLStreamException
494      {
495        if ((stream == null) || (c == null))
496          return null;
497          
498        log.debug("Read XML for class " + c.getName() + " from stream.");
499        
500        JAXBContext jaxb = JAXBContext.newInstance(c);
501          
502        if ((schema == null) && lookForDefaultSchema)
503        {
504          schema = getSchemaFor(c);
505            
506          if ((schema == null) && failIfDefaultSchemaNotFound)
507            throw new JAXBException(schemaNotFoundMessage(c));
508        }
509          
510        Unmarshaller xmlInterpreter = createAndConfigureUnmarshaller(jaxb, schema);
511          
512        XMLStreamReader xmlReader =
513          XMLInputFactory.newInstance().createXMLStreamReader(stream);
514    
515        return xmlInterpreter.unmarshal(xmlReader, c).getValue();
516      }
517    
518      
519      private String schemaNotFoundMessage(Class<?> c)
520      {
521        return "Could not find a default schema file for " + c;
522      }
523      
524      //============================================================================
525      // CONVENIENCE METHODS
526      //============================================================================
527      
528      /**
529       * Returns an XML string for the given object, using {@code schema} for
530       * validation.
531       * <p>
532       * This is a convenience method that is equivalent to calling
533       * {@link #writeObjectAsXmlTo(Writer, Object, Schema)} and using the
534       * {@code Writer} to get a {@code String}.  See that method for more
535       * details about schema and exception handling.</p>
536       * 
537       * @param o the object to be expressed in XML.
538       * @param schema the holder of the rules for validating the XML output.
539       * @return an XML representation of the object {@code o}.
540       * @throws JAXBException if any problems are encountered during the
541       *           transformation to XML.
542       */
543      public String objectToXmlString(Object o, Schema schema)
544        throws JAXBException
545      {
546        StringWriter writer = new StringWriter();
547        
548        writeObjectAsXmlTo(writer, o, schema);
549        
550        return writer.toString();
551      }
552    
553      /**
554       * Returns an XML string for the given object.
555       * This is a convenience method that is equivalent to calling
556       * {@link #objectToXmlString(Object, Schema)
557       *         objectToXmlString(o, (Schema)null)}.
558       *         
559       * @param o the object to be expressed in XML.
560       * @return an XML representation of the object {@code o}.
561       */
562      public String objectToXmlString(Object o) throws JAXBException
563      {
564        return objectToXmlString(o, (Schema)null);
565      }
566      
567      /**
568       * Returns an XML string for the given object, using the schema found in the
569       * given file for validation.
570       * <p>
571       * This is a convenience method that behaves like 
572       * {@link #objectToXmlString(Object, Schema)}, after {@code schemaFileName}
573       * has been turned into a schema.
574       * The location of the schema file is determined by
575       * {@link Class#getResource(java.lang.String)
576       * o.getClass().getResource(schemaFileName)}.
577       * If the schema file cannot be found, a {@link FileNotFoundException} is
578       * thrown.</p>
579       * 
580       * @param o the object to be expressed in XML.
581       * @param schemaFileName the name of the file that holds the schema used to
582       *                       validate the XML transformation.
583       * @return an XML representation of the object {@code o}.
584       * @throws FileNotFoundException if the schema file cannot be found.
585       * @throws JAXBException if any problems are encountered during the
586       *           transformation to XML.
587       */
588      public String objectToXmlString(Object o, String schemaFileName)
589        throws JAXBException, FileNotFoundException
590      {
591        try
592        {
593          Schema schema = getSchema(schemaFileName, o.getClass());
594          return objectToXmlString(o, schema);
595        }
596        catch (SAXException ex)
597        {
598          throw new JAXBException(ex);
599        }
600      }
601      
602      /**
603       * Creates and returns a new object based on the data in an XML file.
604       * <p>
605       * This is a convenience method that is equivalent to calling
606       * {@link #readObjectAsXmlFrom(Reader, Class, Schema)} after turning
607       * {@code fileName} into a {@code Reader}.  See that method for more
608       * details about schema and exception handling.</p>
609       * 
610       * @param <T> the runtime type of the returned object.
611       * @param fileName the name of an XML file.
612       * @param c the class of which the returned object is an instance. 
613       * @param schema the schema used to verify the XML file.
614       * @return an object based on the data in an XML file.
615       * @throws FileNotFoundException if the XML file could not be found.
616       * @throws XMLStreamException if there is a problem opening the XML file,
617       *           if the XML is not well-formed, or for some other
618       *           "unexpected processing conditions".
619       * @throws JAXBException if anything else goes wrong during the
620       *           transformation.
621       */
622      public <T> T xmlFileToObject(String fileName, Class<T> c, Schema schema)
623        throws JAXBException, XMLStreamException, FileNotFoundException
624      {
625        //First try to get file directly...
626        try
627        {
628          return readObjectAsXmlFrom(new FileReader(fileName), c, schema);
629        }
630        //...but if that fails, look in same place where class is located
631        catch (FileNotFoundException ex)
632        {
633          InputStream xmlInput = c.getResourceAsStream(fileName);
634          
635          if (xmlInput == null)
636            throw new FileNotFoundException("Could not find file '"+fileName+"'.");
637          
638          return readObjectAsXmlFrom(xmlInput, c, schema);
639        }
640      }
641    
642      /**
643       * Creates and returns a new object based on the data in an XML file.
644       * This is a convenience method that is equivalent to calling
645       * {@link #xmlFileToObject(String, Class, Schema)
646       *         xmlFileToObject(fileName, c, (Schema)null)}.
647       *         
648       * @param <T> the runtime type of the returned object.
649       * @param fileName the name of an XML file.
650       * @param c the class of which the returned object is an instance. 
651       * @return an object based on the data in an XML file.
652       * @throws FileNotFoundException if the XML file could not be found.
653       * @throws XMLStreamException if there is a problem opening the XML file,
654       *           if the XML is not well-formed, or for some other
655       *           "unexpected processing conditions".
656       * @throws JAXBException if anything else goes wrong during the
657       */
658      public <T> T xmlFileToObject(String fileName, Class<T> c)
659        throws JAXBException, XMLStreamException, FileNotFoundException
660      {
661        Schema schema = lookForDefaultSchema ? getSchemaFor(c) : null;
662        
663        return xmlFileToObject(fileName, c, schema);
664      }
665    
666      //============================================================================
667      // HELPER METHODS
668      //============================================================================
669      
670      /** Helps the objectToXmlString methods. */
671      private Marshaller createAndConfigureMarshaller(JAXBContext jaxb,
672                                                      Schema      schema)
673        throws JAXBException
674      {
675        Marshaller marshaller = jaxb.createMarshaller();
676        
677        if (schema != null)
678          marshaller.setSchema(schema);
679        
680        try
681        {
682          marshaller.setProperty("jaxb.formatted.output", true);
683          marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
684                                 new PrefixMapper());
685        }
686        catch (PropertyException ex)
687        {
688          //Intentional do-nothing:
689          //  1. The JAXB implementation is REQUIRED to support this property.
690          //  2. The inability to format output should not be viewed as exceptional.
691          //  3. The inability to use a preferred prefix is not that important.
692        }
693        
694        return marshaller;
695      }
696      
697      /** Helps the xmlFileToObject methods. */
698      private Unmarshaller createAndConfigureUnmarshaller(JAXBContext jaxb,
699                                                          Schema      schema)
700        throws JAXBException
701      {
702        Unmarshaller unmarshaller = jaxb.createUnmarshaller();
703        
704        if (schema != null)
705          unmarshaller.setSchema(schema);
706        
707        return unmarshaller;
708      }
709    
710      //The @XmlNs annotation, when used in the @XmlSchema, isn't working for us
711      //(as of 2008-Jul-11).  The suggestion to use this means was found on the
712      //jaxb forum at http://forums.java.net/jive/message.jspa?messageID=286044#286044
713      private class PrefixMapper extends com.sun.xml.bind.marshaller.NamespacePrefixMapper
714      {
715        public String getPreferredPrefix(String namespaceUri,
716                                         String suggestion, boolean requirePrefix)
717        {
718          if ("http://www.nrao.edu/namespaces/sss".equals(namespaceUri))
719            return "sss";
720           
721          else if ("http://www.nrc.ca/namespaces/widar".equals(namespaceUri))
722            return "widar";
723          
724          else if ("http://www.nrao.edu/namespaces/nrao".equals(namespaceUri))
725            return "nrao";
726    
727          return suggestion;
728        }
729      }
730      /*
731      public static void main(String[] args) throws Exception
732      {
733        JaxbUtility util = JaxbUtility.getSharedInstance();
734        System.out.println("Schema Location= " + DEFAULT_BASE_URL);
735        //util.setBaseUrlForSchemaFiles(new URL("https://e2e.nrao.edu/schemas/"));
736        System.out.println("Schema = " + util.getSchemaFor(edu.nrao.sss.measure.Distance.class));
737      }
738      */
739    }
740