View Javadoc

1   /*
2    * Copyright (C) 2006  Tom Gibara
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17   */
18  package com.tomgibara.pronto.config.source;
19  
20  import java.io.BufferedInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.URL;
24  import java.net.URLConnection;
25  import java.util.Map;
26  
27  import com.tomgibara.pronto.config.ConfigSource;
28  import com.tomgibara.pronto.util.Arguments;
29  import com.tomgibara.pronto.util.Streams;
30  
31  /**
32   * Accesses a (possibly) remote resource that is been specified via a URL and
33   * parses it into a collection of property values.
34   * 
35   * Instances of this class are not safe for concurrent use. Wrap instances in a
36   * SynchronousConfigSource if thread safety is required.
37   * 
38   * @author Tom Gibara
39   */
40  
41  public class URLConfigSource implements ConfigSource {
42  
43      /**
44       * The URL from which the properties resource is loaded.
45       */
46  
47      private final URL url;
48  
49      /**
50       * The reader which converts the connection streams into properties.
51       */
52  
53      private final PropertiesReader reader;
54  
55      /**
56       * The time at which the properties resource was last modified.
57       * 
58       * Value of -1 indicates never retrieved. Value of 0 indicates retrieved but
59       * unknown.
60       */
61  
62      private long lastModified = -1L;
63  
64      /**
65       * The time at which the properties resource is to expire.
66       * 
67       * Value of -1 indicates never retrieved. Value of 0 indicates retrieved but
68       * unknown.
69       */
70  
71      private long expires = -1L;
72  
73      /**
74       * The properties that were last read from the resource. A null value
75       * indicates that the properties have never before been retrieved.
76       */
77  
78      private Map<String, String> properties = null;
79  
80      // constructors
81  
82      /**
83       * Constructs a source which reads its properties from a URL using a
84       * DefaultPropertiesReader.
85       * 
86       * @param url
87       *            the location of the resource which contains the properties to
88       *            be loaded, not null
89       */
90  
91      public URLConfigSource(final URL url) {
92          this(url, null);
93      }
94  
95      /**
96       * Constructs a source which reads its properties from a URL using the
97       * supplied properties reader. If a null reader is supplied to this
98       * constructor, a DefaultPropertiesReader is used.
99       * 
100      * @param url
101      *            the location of the resource which contains the properties to
102      *            be loaded, not null
103      * @param reader
104      *            a properties reader which can convert the resource into a set
105      *            of properties, may be null
106      */
107 
108     public URLConfigSource(final URL url, final PropertiesReader reader) {
109         Arguments.notNull(url, "url");
110         this.url = url;
111         this.reader = reader == null ? DefaultPropertiesReader.PLAIN : reader;
112     }
113 
114     // accessors
115 
116     /**
117      * The URL from which the properties are loaded.
118      * 
119      * @return the url for this source, never null
120      */
121 
122     public URL getURL() {
123         return url;
124     }
125 
126     /**
127      * The reader which converts this source's resources into properties.
128      * 
129      * @return the properties reader for this source
130      */
131 
132     public PropertiesReader getReader() {
133         return reader;
134     }
135 
136     // config source methods
137 
138     /**
139      * @return the last time at which the resource identified by this source's
140      *         URL was declared to have changed
141      * @throws RuntimeException
142      *             if an IOException was raised when accessing the resource
143      */
144 
145     public long lastModified() throws RuntimeException {
146         update();
147         return lastModified;
148     }
149 
150     /**
151      * @return the properties read from the resource identified by this source's
152      *         URL
153      * @throws RuntimeException
154      *             if an IOException was raised when accessing the resource
155      */
156 
157     public Map<String, String> getProperties() throws RuntimeException {
158         update();
159         return properties;
160     }
161 
162     // protected methods
163 
164     /**
165      * This method is responsible for obtaining a connection to the URL for this
166      * source, configuring it, calling the connect() method on it and then
167      * returning the connection. This method may be overridden to customize the
168      * connection to the URL.
169      * 
170      * The default implementation simply opens the connection, calls connect and
171      * then returns.
172      * 
173      * @return an opened, connected <code>URLConnection</code>
174      * @throws IOException
175      *             if the connection raises an IOException
176      */
177 
178     protected URLConnection connectToURL() throws IOException {
179         URLConnection connection = getURL().openConnection();
180         connection.connect();
181         return connection;
182     }
183 
184     // private utility methods
185 
186     /**
187      * Updates the lastModified, expires and properties fields of this object. A
188      * good attempt is made at
189      * 
190      * 
191      * @throws RuntimeException
192      *             in the event that the an IOException occurs
193      */
194 
195     private void update() throws RuntimeException {
196         long now = 0L;
197         if (properties != null) { // if we already have properties, maybe we
198             // have nothing more to do?
199             if (expires > 0L) {
200                 now = System.currentTimeMillis();
201                 if (now < expires) return; // the resources haven't expired yet
202             }
203         }
204         // fall through to obtain properties - first make sure we know 'now'
205         if (now == 0L) now = System.currentTimeMillis();
206 
207         try {
208             // first: establish a connection
209             URLConnection connection = connectToURL();
210 
211             // TODO check the validity of the response - eg use LMS header
212 
213             // second: identify if the resource has changed
214             boolean changed;
215             long newLastModified = connection.getLastModified();
216             // this tests for one of:
217             // lastModified == -1 (never before read) - implied by:
218             // lastModified < newLastModified (has changed)
219             // newLastModified == 0 (we now don't know the last modified)
220             if (lastModified < newLastModified || newLastModified == 0) {
221                 lastModified = newLastModified;
222                 expires = connection.getExpiration();
223                 changed = true;
224             } else { // there's been no change
225                 // the resource's lease may have changed independently of the
226                 // resource
227                 expires = connection.getExpiration();
228                 changed = false;
229             }
230 
231             // third: read in the properties if necessary
232             // we get the input stream any way because we need to close it
233             InputStream in = null;
234             try {
235                 in = connection.getInputStream();
236                 if (properties == null || changed) {
237                     in = new BufferedInputStream(in);
238                     properties = reader.read(in);
239                 }
240             } finally {
241                 Streams.safeClose(in);
242             }
243         } catch (IOException e) {
244             throw new RuntimeException(e);
245         }
246     }
247 }