View Javadoc

1   /*
2    * Copyright (c) 2003-2005, Henri Yandell
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or 
6    * without modification, are permitted provided that the 
7    * following conditions are met:
8    * 
9    * + Redistributions of source code must retain the above copyright notice, 
10   *   this list of conditions and the following disclaimer.
11   * 
12   * + Redistributions in binary form must reproduce the above copyright notice, 
13   *   this list of conditions and the following disclaimer in the documentation 
14   *   and/or other materials provided with the distribution.
15   * 
16   * + Neither the name of Simple-JNDI nor the names of its contributors 
17   *   may be used to endorse or promote products derived from this software 
18   *   without specific prior written permission.
19   * 
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
23   * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
30   * POSSIBILITY OF SUCH DAMAGE.
31   */
32  package org.osjava.sj.loader;
33  
34  
35  import java.io.File;
36  import java.io.FileInputStream;
37  import java.io.IOException;
38  import java.util.HashMap;
39  import java.util.Hashtable;
40  import java.util.Iterator;
41  import java.util.Map;
42  import java.util.Properties;
43  
44  import javax.naming.Context;
45  import javax.naming.NamingException;
46  
47  import org.osjava.sj.loader.convert.ConvertRegistry;
48  import org.osjava.sj.loader.convert.Converter;
49  
50  import org.osjava.sj.loader.util.AbstractProperties;
51  import org.osjava.sj.loader.util.CustomProperties;
52  import org.osjava.sj.loader.util.IniProperties;
53  import org.osjava.sj.loader.util.Utils;
54  import org.osjava.sj.loader.util.XmlProperties;
55  
56  /***
57   * Loads a .properties file into a JNDI server.
58   */
59  public class JndiLoader {
60  
61      // separator, or just put them in as contexts?
62      public static final String SIMPLE_DELIMITER = "org.osjava.sj.delimiter";
63  
64      // share the same InitialContext
65      public static final String SIMPLE_SHARED = "org.osjava.sj.shared";
66  
67      // char(s) to replace : with on the filesystem in filenames
68      public static final String SIMPLE_COLON_REPLACE = "org.osjava.sj.colon.replace";
69  
70      private static ConvertRegistry convertRegistry = new ConvertRegistry();
71  
72      private Hashtable table = new Hashtable();
73  
74      public JndiLoader() {
75          this.table.put(SIMPLE_DELIMITER, "/");
76      }
77      
78      public JndiLoader(Hashtable env) {
79          if(!env.containsKey(SIMPLE_DELIMITER)) {
80              throw new IllegalArgumentException("The property "+SIMPLE_DELIMITER+" is mandatory. ");
81          }
82          
83  
84          this.table.put(SIMPLE_DELIMITER, env.get(SIMPLE_DELIMITER));
85  
86          if(env.containsKey(SIMPLE_COLON_REPLACE)) {
87              this.table.put(SIMPLE_COLON_REPLACE, env.get(SIMPLE_COLON_REPLACE));
88          }
89  
90      }
91      
92      public void putParameter(String key, String value) {
93          table.put(key, value);
94      }
95  
96      public String getParameter(String key) {
97          return (String) table.get(key);
98      }
99  
100     /***
101      * Loads all .properties files in a directory into a context
102      */
103     public void loadDirectory(File directory, Context ctxt) throws NamingException, IOException {
104         loadDirectory(directory, ctxt, null, "");
105     }
106     public void loadDirectory(File directory, Context ctxt, Context parentCtxt, String ctxtName) throws NamingException, IOException {
107 // System.err.println("Loading directory. ");
108 
109         if( !directory.isDirectory() ) {
110             throw new IllegalArgumentException("java.io.File parameter must be a directory. ["+directory+"]");
111         }
112 
113         File[] files = directory.listFiles();
114         if(files == null) {
115 // System.err.println("Null files. ");
116             return;
117         }
118 
119         for(int i=0; i<files.length; i++) {
120             File file = files[i];
121             String name = file.getName();
122 
123             String colonReplace = (String) this.table.get(SIMPLE_COLON_REPLACE);
124             if(colonReplace != null) {
125                 if(name.indexOf(colonReplace) != -1) {
126                     name = Utils.replace( name, colonReplace, ":" );
127                 }
128             }
129 // System.err.println("Consider: "+name);
130             // TODO: Replace hack with a FilenameFilter
131 
132             if( file.isDirectory() ) {
133                 // HACK: Hack to stop it looking in .svn or CVS
134                 if(name.equals(".svn") || name.equals("CVS")) {
135                     continue;
136                 }
137 
138 // System.err.println("Is directory. Creating subcontext: "+name);
139                 Context tmpCtxt = ctxt.createSubcontext( name );
140                 loadDirectory(file, tmpCtxt, ctxt, name);
141             } else {
142                 // TODO: Make this a plugin system
143                 String[] extensions = new String[] { ".properties", ".ini", ".xml" };
144                 for(int j=0; j<extensions.length; j++) {
145                     String extension = extensions[j];
146                     if( file.getName().endsWith(extension) ) {
147 // System.err.println("Is "+extension+" file. "+name);
148                         Context tmpCtxt = ctxt;
149                         if(!file.getName().equals("default"+extension)) {
150                             name = name.substring(0, name.length() - extension.length());
151 // System.err.println("Not default, so creating subcontext: "+name);
152                             tmpCtxt = ctxt.createSubcontext( name );
153                             parentCtxt = ctxt;
154                             ctxtName = name;
155                         }
156                         load( loadFile(file), tmpCtxt, parentCtxt, ctxtName );
157                     }
158                 }
159             }
160         }
161 
162     }
163 
164     private Properties loadFile(File file) throws IOException {
165 //        System.err.println("LOADING: "+file);
166         AbstractProperties p = null;
167 
168         if(file.getName().endsWith(".xml")) {
169             p = new XmlProperties();
170         } else
171         if(file.getName().endsWith(".ini")) {
172             p = new IniProperties();
173         } else {
174             p = new CustomProperties();
175         }
176 
177         p.setDelimiter( (String) this.table.get(SIMPLE_DELIMITER) );
178 
179         FileInputStream fin = null;
180         try {
181             fin = new FileInputStream(file);
182             p.load(fin);
183             return p;
184         } finally {
185             if(fin != null) fin.close();
186         }
187     }
188 
189 
190     /***
191      * Loads a properties object into a context.
192      */
193     public void load(Properties properties, Context ctxt) throws NamingException {
194         load(properties, ctxt, null, "");
195     }
196     public void load(Properties properties, Context ctxt, Context parentCtxt, String ctxtName) throws NamingException {
197  //       System.err.println("Loading Properties: " + properties);
198 
199         String delimiter = (String) this.table.get(SIMPLE_DELIMITER);
200         String typePostfix = delimiter + "type";
201 
202         // NOTE: "type" effectively turns on pseudo-nodes; if it 
203         //       isn't there then other pseudo-nodes will result 
204         //       in re-bind errors
205 
206         // scan for pseudo-nodes, aka "type":   foo.type
207         // store in a temporary type table:    "foo", new Properties() with type="value"
208         Map typeMap = new HashMap();
209         Iterator iterator = properties.keySet().iterator();
210         while(iterator.hasNext()) {
211             String key = (String) iterator.next();
212 
213             if(key.equals("type") || key.endsWith( typePostfix )) {
214                 Properties tmp = new Properties();
215                 tmp.put( "type", properties.get(key) );
216                 if( key.equals( "type" ) ) {
217                     typeMap.put( "", tmp );
218                 } else {
219                     typeMap.put( key.substring(0, key.length() - typePostfix.length()), tmp );
220                 }
221             }
222 
223         }
224 
225 //        System.err.println("TypeMap: " + typeMap);
226 
227 // if it matches a type root, then it should be added to the properties
228 // if not, then it should be placed in the context
229 // for each type properties
230 // call convert: pass a Properties in that contains everything starting with foo, but without the foo
231 // put objects in context
232 
233         iterator = properties.keySet().iterator();
234         while(iterator.hasNext()) {
235             String key = (String) iterator.next();
236             Object value = properties.get(key);
237 
238             if(key.equals("type") || key.endsWith( typePostfix )) {
239                 continue;
240             }
241 
242             if(typeMap.containsKey(key)) {
243 // System.err.println("Typed: "+key);
244                 ( (Properties) typeMap.get(key) ).put("", value);
245                 continue;
246             }
247 
248             if(key.indexOf(delimiter) != -1) {
249                 String pathText = removeLastElement( key, delimiter );
250                 String nodeText = getLastElement( key, delimiter );
251 // System.err.println("PathText: "+pathText+" NodeText: "+nodeText);
252 
253                 if(typeMap.containsKey(pathText)) {
254 // System.err.println("Sibling: "+key);
255                     ( (Properties) typeMap.get(pathText) ).put(nodeText, value);
256                     continue;
257                 }
258             } else
259             if(typeMap.containsKey("")) {
260 // System.err.println("Empty Sibling: "+key);
261                     ( (Properties) typeMap.get("") ).put(key, value);
262                     continue;
263             }
264 
265 // System.err.println("Putting: "+key);
266             jndiPut( ctxt, key, properties.get(key) );
267         }
268 
269         Iterator typeIterator = typeMap.keySet().iterator();
270         while(typeIterator.hasNext()) {
271             String typeKey = (String) typeIterator.next();
272             Properties typeProperties = (Properties) typeMap.get(typeKey);
273 
274             Object value = convert(typeProperties);
275 // System.err.println("Putting typed: "+typeKey);
276             if(typeKey.equals("")) {
277                 jndiPut( parentCtxt, ctxtName, value );
278             } else {
279                 jndiPut( ctxt, typeKey, value );
280             }
281         }
282 
283     }
284 
285     private void jndiPut(Context ctxt, String key, Object value) throws NamingException {
286         // here we need to break by the specified delimiter
287 //        System.err.println("Putting "+key+"="+value);
288 
289         // can't use String.split as the regexp will clash with the types of chars 
290         // used in the delimiters. Could use Commons Lang. Quick hack instead.
291 //        String[] path = key.split( (String) this.table.get(SIMPLE_DELIMITER) );
292         String[] path = Utils.split( key, (String) this.table.get(SIMPLE_DELIMITER) );
293 
294 // System.err.println("LN: "+path.length);
295         int lastIndex = path.length - 1;
296 
297 
298         Context tmpCtxt = ctxt;
299 
300         for(int i=0; i < lastIndex; i++) {
301             Object obj = tmpCtxt.lookup(path[i]);
302             if(obj == null) {
303 // System.err.println("Creating subcontext: " + path[i] + " for " + key);
304                 tmpCtxt = tmpCtxt.createSubcontext(path[i]);
305             } else
306             if(obj instanceof Context) {
307 // System.err.println("Using subcontext: "+obj + " for " + key);
308                 tmpCtxt = (Context) obj;
309             } else {
310                 throw new RuntimeException("Illegal node/branch clash. At branch value '"+path[i]+"' an Object was found: " +obj);
311             }
312         }
313         
314         Object obj = tmpCtxt.lookup(path[lastIndex]);
315         if(obj instanceof Context) {
316             tmpCtxt.destroySubcontext(path[lastIndex]);
317             obj = null;
318         }
319         if(obj == null) {
320 // System.err.println("Binding: "+path[lastIndex]+" on "+key);
321             tmpCtxt.bind( path[lastIndex], value );
322         } else {
323 // System.err.println("Rebinding: "+path[lastIndex]+" on "+key);
324             tmpCtxt.rebind( path[lastIndex], value );
325         }
326     }
327 
328     private static Object convert(Properties properties) {
329         String type = properties.getProperty("type");
330         // TODO: handle a plugin type system
331         
332         String converterClassName = properties.getProperty("converter");
333         if(converterClassName != null) {
334             try {
335                 Class converterClass = Class.forName( converterClassName );
336                 Converter converter = (Converter) converterClass.newInstance();
337                 return converter.convert(properties, type);
338             } catch(ClassNotFoundException cnfe) {
339                 throw new RuntimeException("Unable to find class: "+converterClassName, cnfe);
340             } catch(IllegalAccessException ie) {
341                 throw new RuntimeException("Unable to access class: "+type, ie);
342             } catch(InstantiationException ie) {
343                 throw new RuntimeException("Unable to create Converter " + type + " via empty constructor. ", ie);
344             }
345         }
346 
347         // TODO: Support a way to set the default converters in the jndi.properties 
348         //       and in the API itself
349         Converter converter = convertRegistry.getConverter(type);
350         if(converter != null) {
351             return converter.convert(properties, type);
352         }
353 
354         return properties.get("");
355 
356     }
357 
358     // String methods to make the using code more readable
359     private static String getLastElement( String str, String delimiter ) {
360         int idx = str.lastIndexOf(delimiter);
361         return str.substring(idx + 1);
362     }
363     private static String removeLastElement( String str, String delimiter ) {
364         int idx = str.lastIndexOf(delimiter);
365         return str.substring(0, idx);
366     }
367 
368 }