001/*
002 * ConfigurationFileLoader.java
003 *
004 * Created on 28. April 2002, 14:45
005 */
006
007package org.jconfig;
008
009import java.io.*;
010import java.util.HashMap;
011import java.util.Hashtable;
012import java.util.Vector;
013import java.util.Iterator;
014import java.util.Enumeration;
015import java.util.Properties;
016import javax.xml.parsers.*;
017import org.xml.sax.*;
018import org.xml.sax.helpers.*;
019import org.w3c.dom.*;
020/**
021 *
022 * @author  Andreas Mecky andreas.mecky@xcom.de
023 * @author Terry Dye terry.dye@xcom.de
024 */
025public class ConfigurationFileHandler implements ConfigurationHandler {
026
027    // separator for category and property for HashMap
028    private static final char C_SEP   = 127; 
029    // prefix for category names
030                private static final char C_PFIX  =  64; 
031    private boolean validate = false;
032    private HashMap initValues;
033    private HashMap props;
034    private Vector categories;
035    /** Creates a new instance of ConfigurationFileLoader */
036    public ConfigurationFileHandler() {
037    }
038    
039    /**
040     * @param parameters  */    
041    public void initialize(HashMap parameters) {
042        initValues = parameters;
043        props = new HashMap();
044        categories = new Vector();
045    }
046    
047    public void setValidation(boolean validate) {
048        this.validate = validate;
049    }
050    
051    /**
052     * @throws ConfigurationManagerException  */    
053    public synchronized void load() throws ConfigurationManagerException {        
054        if ( initValues.get("File") != null ) {
055            load((File)initValues.get("File"));
056        }
057        else if ( initValues.get("InputStream") != null ) {
058            load((InputStream)initValues.get("InputStream"));
059        }
060        else if ( initValues.get("URL") != null ) {
061            load((String)initValues.get("URL"));
062        }
063    }
064    
065    /**
066     * @param file
067     * @throws ConfigurationManagerException  */    
068    public synchronized void load(File file) throws ConfigurationManagerException {        
069        if ( file == null ) {
070            return;
071        }        
072        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
073        // set the validation flag (true if it should be validated)
074        dbf.setValidating(validate);
075        DocumentBuilder db = null;
076        try {
077            db = dbf.newDocumentBuilder();
078        } catch (ParserConfigurationException pce) {
079            throw new ConfigurationManagerException("The parser cannot create a new document builder: " + pce.getMessage());
080        }
081        // if we have to validate then we need to define an error handler here
082        if (validate) {
083            try {
084                // Set an ErrorHandler before parsing
085                OutputStreamWriter errorWriter =
086                        new OutputStreamWriter(System.err, "UTF-8");
087                db.setErrorHandler(
088                        new MyErrorHandler(new PrintWriter(errorWriter, true)));
089            } catch (Exception e) {
090                throw new ConfigurationManagerException("The parser cannot set up the error handler");
091            }
092        }
093        Document doc = null;
094        try {
095            doc = db.parse(file);
096        } catch (SAXException se) {
097            throw new ConfigurationManagerException("The parser cannot parse the file: " + se.getMessage());
098        } catch (IOException ioe) {
099            throw new ConfigurationManagerException("The parser cannot open the file: " + ioe.getMessage());
100        }
101        processProperties(doc);
102
103    }
104                
105    /**
106     *  Gets the variables attribute of the ConfigurationManager object
107     *
108     *@param  doc  Description of the Parameter
109     *@return      The variables value
110     */
111    private Hashtable getVariables(Document doc) {
112        Hashtable vars = null;
113        NodeList nl = doc.getElementsByTagName("variables");
114        for (int i = 0; i < nl.getLength(); i++) {
115            Node n = nl.item(i);
116            vars = new Hashtable();
117            for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
118                // get all vraibles
119                if (child.getNodeName().equals("variable")) {
120                    NamedNodeMap myAtt = child.getAttributes();
121                    Node myNode = myAtt.getNamedItem("name");
122                    String name = myNode.getNodeValue();
123                    myNode = myAtt.getNamedItem("value");
124                    String value = myNode.getNodeValue();
125                    if (name != null && value != null) {
126                        // we store the name as ${name} so we have
127                        // it directly the way it is used
128                        String rname = "${" + name + "}";
129                        vars.put(rname, value);
130                    }
131                }
132            }
133        }
134        return vars;
135    }
136
137
138    /**
139     *  Description of the Method
140     *
141     *@param  vars  Description of the Parameter
142     *@param  line  Description of the Parameter
143     *@return       Description of the Return Value
144     */
145    private String replaceVariables(Hashtable vars, String line) {
146        if (vars != null) {
147            Enumeration keys = vars.keys();
148            int pos = 0;
149            // walk through all variables
150            while (keys.hasMoreElements()) {
151                String currentKey = (String) keys.nextElement();
152                String value = (String) vars.get((String) currentKey);
153                pos = line.indexOf(currentKey);
154                // check if we have found a variable
155                if (pos != -1) {
156                    // cut the line into 2 pieces and put in the
157                    // value of the variable
158                    String firstPart = line.substring(0, pos);
159                    String secondPart = line.substring(pos + currentKey.length());
160                    line = firstPart + value + secondPart;
161                }
162            }
163        }
164        return line;
165    }
166
167
168    /**
169     *  Description of the Method
170     *
171     *@param  doc  Description of the Parameter
172     */
173    private void processProperties(Document doc) {        
174        String currentCategory;
175        Hashtable myvars = getVariables(doc);
176        // first we get all nodes where the element is category
177        NodeList nl = doc.getElementsByTagName("category");
178        for (int i = 0; i < nl.getLength(); i++) {
179            // now we get every node from the list
180            Node n = nl.item(i);
181            // and get the name attribute for this category
182            NamedNodeMap curAtt = n.getAttributes();
183            Node curNode = curAtt.getNamedItem("name");
184            currentCategory = curNode.getNodeValue();
185            categories.add(currentCategory);            
186            // now we process all children for this category
187            for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
188                if (child.getNodeName().equals("property")) {
189                    // we have found a property element and now we grab the name and value
190                    // attributes
191                    NamedNodeMap myAtt = child.getAttributes();
192                    Node myNode = myAtt.getNamedItem("name");
193                    String name = myNode.getNodeValue();
194                    myNode = myAtt.getNamedItem("value");
195                    String value = myNode.getNodeValue();
196                    // if we have both then lets store it
197                    if (name != null && value != null) {
198                        // the key is always category/name
199                        // e.g. general/congig_file                        
200                        props.put(C_PFIX+currentCategory+C_SEP+name,replaceVariables(myvars, value));
201                    }
202                }
203            }
204        }
205    }
206
207    /**
208     *  This method will read in a file and generate the properties
209     *
210     *@param  is                              The inputstream
211     *@throws  ConfigurationManagerException  if the file cannot be processed
212     */
213    public synchronized void load(InputStream is) throws ConfigurationManagerException {            
214        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
215        // set the validation flag (true if it should be validated)
216        dbf.setValidating(validate);
217        DocumentBuilder db = null;
218        try {
219            db = dbf.newDocumentBuilder();
220        } catch (ParserConfigurationException pce) {
221            throw new ConfigurationManagerException("The parser cannot create a new document builder: " + pce.getMessage());
222        }
223        // if we have to validate then we need to define an error handler here
224        if (validate) {
225            try {
226                // Set an ErrorHandler before parsing
227                OutputStreamWriter errorWriter =
228                        new OutputStreamWriter(System.err, "UTF-8");
229                db.setErrorHandler(
230                        new MyErrorHandler(new PrintWriter(errorWriter, true)));
231            } catch (Exception e) {
232                throw new ConfigurationManagerException("The parser cannot set up the error handler");
233            }
234        }
235        Document doc = null;
236        try {
237            doc = db.parse(is);
238        } catch (SAXException se) {
239            throw new ConfigurationManagerException("The parser cannot parse the file: " + se.getMessage());
240        } catch (IOException ioe) {
241            throw new ConfigurationManagerException("The parser cannot open the file: " + ioe.getMessage());
242        }
243        processProperties(doc);
244
245    }
246
247
248    /**
249     *  This method will read the content from an URL and generate the
250     *  properties. If you have the need to use a proxy server, create
251     *  jconfig.properties file and place it inside your system path.
252     *  <BR />
253     *  jconfig.properties example:<BR />
254     *  # jconfig.properties file<BR />
255     *  http.proxyHost=proxy.server.url<BR />
256     *  http.proxyPort=3128
257     *
258     *@param  theURL                          Description of the Parameter
259     *@throws  ConfigurationManagerException  if the file cannot be processed
260     */
261    public synchronized void load(String theURL) throws ConfigurationManagerException {        
262        java.net.URL jcfURL = null;
263        InputStream is = null;
264        try {
265            // get a jconfig.properties in classpath, if it exists
266            ClassLoader cl = this.getClass().getClassLoader();
267            InputStream jcf = cl.getResourceAsStream( "jconfig.properties" );            
268            // it is possible that the jconfig.properties does not exist, we get null
269            if ( jcf != null ) {
270                Properties jcfProperties = new Properties();
271                jcfProperties.load( jcf );
272            
273                // load what is set in system
274                Properties prop = System.getProperties(); 
275                // if we see http.proxyHost and/or http.proxyPort inside
276                // the jconfig.properties, we can set the System.properties
277                // for use by the URLConnection object
278                if ( jcfProperties.getProperty( "http.proxyHost" ) != null )
279                    prop.put( "http.proxyHost", jcfProperties.getProperty( "http.proxyHost" ) );
280                if ( jcfProperties.getProperty( "http.proxyPort" ) != null )
281                    prop.put( "http.proxyPort", jcfProperties.getProperty( "http.proxyPort" ) );
282            }
283            // prepare URL, open the connection and grab stream for parsing
284            jcfURL = new java.net.URL( theURL );
285            java.net.URLConnection con = jcfURL.openConnection();
286            is = con.getInputStream();
287        }
288        catch ( Exception e ) {
289            throw new ConfigurationManagerException ( "Problem with URL handling/connection/validating: "
290                + e.getMessage() );
291        }
292        
293        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
294        dbf.setValidating(validate);
295        DocumentBuilder db = null;
296        try {
297            db = dbf.newDocumentBuilder();
298        } catch (ParserConfigurationException pce) {
299            throw new ConfigurationManagerException("The parser cannot create a new document builder: " + pce.getMessage());
300        }
301        // if we have to validate then we need to define an error handler here
302        if (validate) {
303            try {
304                // Set an ErrorHandler before parsing
305                OutputStreamWriter errorWriter =
306                        new OutputStreamWriter(System.err, "UTF-8");
307                db.setErrorHandler(
308                        new MyErrorHandler(new PrintWriter(errorWriter, true)));
309            } catch (Exception e) {
310                throw new ConfigurationManagerException("The parser cannot set up the error handler");
311            }
312        }
313        Document doc = null;
314        try {
315            doc = db.parse(is);
316        } catch (SAXException se) {
317            throw new ConfigurationManagerException("The parser cannot parse the XML: " + se.getMessage());
318        } catch (IOException ioe) {
319            throw new ConfigurationManagerException("The parser cannot open the file: " + ioe.getMessage());
320        }
321        processProperties(doc);
322
323    }
324
325    /**
326     *  This method will save the current categories and their properties to a
327     *  file
328     *
329     *@param  file                               the file that will be generated
330     *@exception  ConfigurationManagerException  Description of the Exception
331     */
332    public void store(File file,HashMap data) throws ConfigurationManagerException {
333        // this will store the properties for a certain category             
334        Iterator it = data.keySet().iterator();
335        try {
336            // let's open the file
337            FileWriter fw = new FileWriter(file);
338            // and write the header
339            fw.write("<?xml version=\"1.0\" ?>\n");
340            fw.write("<properties>\n");
341            // now we walk through all categories
342            while ( it.hasNext() ) {
343                String currentCategory = (String)it.next();
344                fw.write("  <category name='" + currentCategory + "'>\n");
345                // now we get all properties for this category
346                Hashtable props = (Hashtable)data.get(currentCategory);                
347                Enumeration lprops = props.keys();
348                // here we walk through the properties
349                while (lprops.hasMoreElements()) {
350                    String currentKey = (String) lprops.nextElement();
351                    // and write it to the file
352                    fw.write("    <property name='" + currentKey + "' value='" + (String) props.get(currentKey) + "'/>\n");
353                }
354                fw.write("  </category>\n");
355            }
356            fw.write("</properties>\n");
357            // close the file because we are done
358            fw.close();
359        } catch (Exception e) {
360            throw new ConfigurationManagerException("The file cannot be saved");
361        }         
362    }
363
364    /**
365     * This method should return a <code>Vector</code> of all
366     * categories. The category names must be stored as <code>Strings</code>
367     * inside the <code>Vector</code>.
368     *
369     * @return all categories inside a <code>Vector</code>
370     */
371    public Vector getCategories() {
372        return categories;
373    }    
374    
375    /**
376     * This method should return all properties that was read
377     * by the particular handler.<BR />
378     * The key for the properties are build like:<BR />
379     * category/property name.
380     *
381     * @return a <code>HashMap</code> with all properties.
382     */
383    public HashMap getProperties() {
384        return props;
385    }
386    
387    /**
388     * This method should store all categories and properties.
389     */
390    public void store() throws ConfigurationManagerException {
391        store((File)initValues.get("File"),(HashMap)initValues.get("Data"));
392    }
393    
394    //
395    // This is an inner class
396    //
397    // Error handler to report errors and warnings
398    //
399    /**
400     *  Description of the Class
401     *
402     *@author     andreas
403     *@created    27. Januar 2002
404     */
405    private static class MyErrorHandler implements ErrorHandler {
406
407        private PrintWriter out;
408
409
410        /**
411         *  Constructor for the MyErrorHandler object
412         *
413         *@param  out  Description of the Parameter
414         */
415        MyErrorHandler(PrintWriter out) {
416            this.out = out;
417        }
418
419
420        /**
421         *  Returns a string describing parse exception details
422         *
423         *@param  spe  Description of the Parameter
424         *@return      The parseExceptionInfo value
425         */
426        private String getParseExceptionInfo(SAXParseException spe) {
427            String systemId = spe.getSystemId();
428            if (systemId == null) {
429                systemId = "null";
430            }
431            String info = "URI=" + systemId +
432                    " Line=" + spe.getLineNumber() +
433                    ": " + spe.getMessage();
434            return info;
435        }
436
437
438        // The following methods are standard SAX ErrorHandler methods.
439        // See SAX documentation for more info.
440
441        /**
442         *  Description of the Method
443         *
444         *@param  spe               Description of the Parameter
445         *@exception  SAXException  Description of the Exception
446         */
447        public void warning(SAXParseException spe) throws SAXException {
448            out.println("Warning: " + getParseExceptionInfo(spe));
449        }
450
451
452        /**
453         *  Description of the Method
454         *
455         *@param  spe               Description of the Parameter
456         *@exception  SAXException  Description of the Exception
457         */
458        public void error(SAXParseException spe) throws SAXException {
459            String message = "Error: " + getParseExceptionInfo(spe);
460            throw new SAXException(message);
461        }
462
463
464        /**
465         *  Description of the Method
466         *
467         *@param  spe               Description of the Parameter
468         *@exception  SAXException  Description of the Exception
469         */
470        public void fatalError(SAXParseException spe) throws SAXException {
471            String message = "Fatal Error: " + getParseExceptionInfo(spe);
472            throw new SAXException(message);
473        }
474    }
475
476}