1   /*
2    * Copyright 2002-2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.pdark.dsmp;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.commons.lang.SystemUtils;
29  import org.apache.log4j.Logger;
30  import org.jdom.Document;
31  import org.jdom.Element;
32  import org.jdom.JDOMException;
33  import org.jdom.input.SAXBuilder;
34  
35  /**
36   * Read and manage the configuration.
37   * 
38   * <p>Unlike the standard config classes, this one allows to reload
39   * the config at any convenient time.
40   * 
41   * @author digulla
42   *
43   */
44  public class Config
45  {
46      public static final Logger log = Logger.getLogger(Config.class);
47      
48      private static Document config;
49      private static long configLastModified;
50      private static String BASE_DIR = null;
51      private static int serverPort = 8080;
52      private static String proxyHost = "proxy";
53      private static int proxyPort = 80;
54      private static String proxyUser;
55      private static String proxyPassword;
56      private static File cacheDirectory = new File ("cache");
57      private static File patchesDirectory = new File ("patches");
58      
59      public static synchronized void reload ()
60      {
61          String fileName = System.getProperty("dsmp.conf", "dsmp.conf");
62          File configFile = new File (fileName);
63          if (!configFile.isAbsolute())
64              configFile = new File (getBaseDirectory(), fileName);
65          
66          // TODO This means one access to the file system per Config method call
67          long lastModified = configFile.lastModified();
68          if (config == null || lastModified != configLastModified)
69          {
70              log.info((config == null ? "Loading" : "Reloading")+ " config from "+configFile.getAbsolutePath());
71              configLastModified = lastModified;
72  
73              SAXBuilder builder = new SAXBuilder ();
74              Throwable t = null;
75              Document doc = config;
76              List<MirrorEntry> tmpMirrors = mirrors;
77              List<AllowDeny> tmpAllowDeny = allowDeny;
78              String[] tmpNoProxy = noProxy;
79              int tmpPort = serverPort;
80              String tmpProxyHost = proxyHost;
81              int tmpProxyPort = proxyPort;
82              String tmpProxyUser = proxyUser;
83              String tmpProxyPassword = proxyPassword;
84              File tmpCacheDirectory = cacheDirectory;
85              File tmpPatchesDirectory = patchesDirectory;
86              
87              try
88              {
89                  doc = builder.build(configFile);
90                  Element root = doc.getRootElement ();
91                  tmpCacheDirectory = getCacheDirectory (root);
92                  tmpPatchesDirectory = getPatchesDirectory (root);
93                  tmpMirrors = getMirrors (root);
94                  tmpAllowDeny = getAllowDeny (root);
95                  tmpNoProxy = getNoProxy (root);
96                  tmpPort = getPort (root);
97                  tmpProxyHost = getProxyHost (root);
98                  tmpProxyPort = getProxyPort (root);
99                  tmpProxyUser = getProxyUsername (root);
100                 tmpProxyPassword = getProxyPassword (root);
101             }
102             catch (JDOMException e)
103             {
104                 t = e;
105             }
106             catch (IOException e)
107             {
108                 t = e;
109             }
110             
111             if (t != null)
112             {
113                 String msg = "Error loading config from "+configFile.getAbsolutePath();
114                 log.error (msg, t);
115                 if (config == null)
116                     throw new Error (msg, t);
117             }
118 
119             // TODO All options should be checked for errors
120 
121             // After the error checking, save the new parameters
122             config = doc;
123             cacheDirectory = tmpCacheDirectory;
124             patchesDirectory = tmpPatchesDirectory;
125             mirrors = tmpMirrors;
126             allowDeny = tmpAllowDeny;
127             noProxy = tmpNoProxy;
128             serverPort = tmpPort;
129             proxyHost = tmpProxyHost;
130             proxyPort = tmpProxyPort;
131             proxyUser = tmpProxyUser;
132             proxyPassword = tmpProxyPassword;
133         }
134     }
135     
136     public static void setBaseDir (String path)
137     {
138         Config.BASE_DIR = path;
139     }
140     
141     private static int getPort (Element root)
142     {
143         int port = getIntProperty (root, "server", "port", 8080);
144         int max = 0xffff;
145         if (port < 1 || port > max)
146             throw new RuntimeException ("Value for proxy.port must be between 1 and "+max);
147         return port;
148     }
149     
150     public static int getPort ()
151     {
152         return serverPort;
153     }
154 
155     private static File getCacheDirectory (Element root)
156     {
157         String defaultValue = "cache";
158         
159         String s = getStringProperty(root, "directories", "cache", defaultValue);
160         File f = new File (s);
161         if (!f.isAbsolute())
162             f = new File (getBaseDirectory (), s);
163         
164         IOUtils.mkdirs (f);
165         
166         return f;
167     }
168     
169     public static File getCacheDirectory ()
170     {
171         return cacheDirectory;
172     }
173     
174     private static File getPatchesDirectory (Element root)
175     {
176         String defaultValue = "patches";
177         
178         String s = getStringProperty(root, "directories", "patches", defaultValue);
179         File f = new File (s);
180         if (!f.isAbsolute())
181             f = new File (getBaseDirectory (), s);
182         
183         IOUtils.mkdirs (f);
184         
185         return f;
186     }
187 
188     public static File getPatchesDirectory ()
189     {
190         return patchesDirectory;
191     }
192     
193     public static File getBaseDirectory ()
194     {
195         String path = BASE_DIR;
196         if (path == null)
197             path = SystemUtils.USER_HOME;
198         return new File (path);
199     }
200 
201     private static String getStringProperty (Element root, String element, String attribute, String defaultValue)
202     {
203         Element e = root.getChild(element);
204         if (e == null)
205             return defaultValue;
206         
207         String value = e.getAttributeValue(attribute);
208         if (value == null)
209             return defaultValue;
210         
211         return value;
212     }
213     
214     private static boolean hasProperty (Element root, String element)
215     {
216         Element e = root.getChild(element);
217         if (e == null)
218             return false;
219         
220         return true;
221     }
222     
223     private static String getStringProperty (Element root, String element, String attribute)
224     {
225         String value = getStringProperty(root, element, attribute, null);
226         if (value == null)
227             throw new RuntimeException ("Property "+element+"@"+attribute+" is not set.");
228         
229         return value;
230     }
231     
232     private static int getIntProperty (Element root, String element, String attribute, int defaultValue)
233     {
234         String value = getStringProperty(root, element, attribute, null);
235         if (value == null)
236             return defaultValue;
237         
238         try
239         {
240             return Integer.parseInt(value);
241         }
242         catch (NumberFormatException e)
243         {
244             throw (NumberFormatException)(new NumberFormatException ("Error convertion value '"+value+"' of property "+element+"@"+attribute+": "+e.getMessage()).initCause(e));
245         }
246     }
247 
248     private static boolean hasProxy (Element root)
249     {
250         return hasProperty (root, "proxy");
251     }
252     
253     private static String getProxyUsername (Element root)
254     {
255         if (!hasProxy (root))
256             return null;
257         
258         return getStringProperty (root, "proxy", "user");
259     }
260 
261     public static String getProxyUsername ()
262     {
263         return proxyUser;
264     }
265     
266     private static String getProxyPassword (Element root)
267     {
268         if (!hasProxy (root))
269             return null;
270 
271         return getStringProperty (root, "proxy", "password");
272     }
273 
274     public static String getProxyPassword ()
275     {
276         return proxyPassword;
277     }
278     
279     private static String getProxyHost (Element root)
280     {
281         if (!hasProxy (root))
282             return null;
283 
284         return getStringProperty (root, "proxy", "host");
285     }
286     
287     public static String getProxyHost ()
288     {
289         return proxyHost;
290     }
291     
292     private static int getProxyPort (Element root)
293     {
294         int port = getIntProperty (root, "proxy", "port", 80);
295         int max = 0xffff;
296         if (port < 1 || port > max)
297             throw new RuntimeException ("Value for proxy.port must be between 1 and "+max);
298         return port;
299     }
300     
301     public static int getProxyPort ()
302     {
303         return proxyPort;
304     }
305     
306     private static class MirrorEntry
307     {
308         private String from;
309         private String to;
310         
311         public MirrorEntry (String from, String to)
312         {
313             this.from = fix (from);
314             this.to = fix (to);
315         }
316 
317         private String fix (String s)
318         {
319             s = s.trim ();
320             if (!s.endsWith("/"))
321                 s += "/";
322             return s;
323         }
324         
325         public URL getMirrorURL (String s)
326         {
327             //log.debug(s);
328             //log.debug(from);
329             
330             if (s.startsWith(from))
331             {
332                 s = s.substring(from.length());
333                 s = to + s;
334                 try
335                 {
336                     return new URL (s);
337                 }
338                 catch (MalformedURLException e)
339                 {
340                     throw new RuntimeException ("Couldn't create URL from "+s, e);
341                 }
342             }
343             
344             return null;
345         }
346     }
347     
348     private static List<MirrorEntry> mirrors = Collections.emptyList ();
349     
350     public static List<MirrorEntry> getMirrors (Element root)
351     {
352         List<MirrorEntry> l = new ArrayList<MirrorEntry> ();
353         for (Iterator iter = root.getChildren("redirect").iterator(); iter.hasNext();)
354         {
355             Element element = (Element)iter.next();
356             String from = element.getAttributeValue("from");
357             String to = element.getAttributeValue("to");
358             
359             if (StringUtils.isBlank(from))
360                 throw new RuntimeException ("from attribute is missing or empty in redirect element");
361             if (StringUtils.isBlank(to))
362                 throw new RuntimeException ("to attribute is missing or empty in redirect element");
363             
364             l.add (new MirrorEntry (from, to));
365         }
366 
367         return l;
368     }
369     
370     public static List<MirrorEntry> getMirrors ()
371     {
372         return mirrors;
373     }
374     
375     public static URL getMirror (URL url) throws MalformedURLException
376     {
377         String s = url.toString();
378         
379         for (MirrorEntry entry: getMirrors())
380         {
381             URL mirror = entry.getMirrorURL(s);
382             if (mirror != null)
383             {
384                 log.info ("Redirecting request to mirror "+mirror.toString());
385                 return mirror;
386             }
387         }
388         
389         return url;
390     }
391     
392     private static String[] noProxy = new String[0];
393     
394     private static String[] getNoProxy (Element root)
395     {
396         String s = getStringProperty(root, "proxy", "no-proxy", null);
397         if (s == null)
398             return new String[0];
399         
400         String[] result = StringUtils.split(s, ",");
401         for (int i=0; i<result.length; i++)
402         {
403             result[i] = result[i].trim ();
404         }
405         
406         return result;
407     }
408     
409     public static String[] getNoProxy ()
410     {
411         return noProxy;
412     }
413     
414     public static boolean useProxy (URL url)
415     {
416         if (!hasProxy (config.getRootElement ()))
417             return false;
418         
419         String host = url.getHost();
420         for (String postfix: getNoProxy())
421         {
422             if (host.endsWith(postfix))
423                 return false;
424         }
425         return true;
426     }
427 
428     private static class AllowDeny
429     {
430         private final String url;
431         private boolean allow;
432         
433         public AllowDeny (String url, boolean allow)
434         {
435             this.url = url;
436             this.allow = allow;
437         }
438         
439         public boolean matches (String url)
440         {
441             return url.startsWith(this.url);
442         }
443         
444         public boolean isAllowed ()
445         {
446             return allow;
447         }
448         
449         public String getURL ()
450         {
451             return url;
452         }
453     }
454     
455     private static List<AllowDeny> allowDeny = Collections.emptyList ();
456     
457     public static List<AllowDeny> getAllowDeny (Element root)
458     {
459         ArrayList<AllowDeny> l = new ArrayList<AllowDeny> ();
460         
461         for (Iterator iter = root.getChildren().iterator(); iter.hasNext();)
462         {
463             Element element = (Element)iter.next();
464             if ("allow".equals (element.getName()) || "deny".equals(element.getName()))
465             {
466                 boolean allow = "allow".equals (element.getName());
467                 String url = element.getAttributeValue("url");
468                 if (url == null)
469                     throw new RuntimeException ("Missing or empty url attribute in "+element.getName()+" element");
470                 l.add (new AllowDeny (url, allow));
471             }
472         }
473 
474         return l;
475     }
476     
477     public static List<AllowDeny> getAllowDeny ()
478     {
479         return allowDeny;
480     }
481     
482     public static boolean isAllowed (URL url)
483     {
484         String s = url.toString();
485         for (AllowDeny rule: getAllowDeny())
486         {
487             if (rule.matches(s))
488             {
489                 log.info((rule.isAllowed() ? "Allowing" : "Denying")+" access to "+url+" because of config rule");
490                 return rule.isAllowed();
491             }
492         }
493         
494         return true;
495     }
496 }