View Javadoc

1   /*
2    * The SmartConfig Library
3    * Copyright (C) 2004-2006
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   *
19   * For further informations on the SmartConfig Library please visit
20   *
21   *                        http://smartconfig.sourceforge.net
22   */
23  package net.smartlab.config;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.ObjectInputStream;
30  import java.io.ObjectOutputStream;
31  import java.io.StringReader;
32  import java.net.MalformedURLException;
33  import java.net.URL;
34  import java.security.InvalidKeyException;
35  import java.security.Key;
36  import java.security.NoSuchAlgorithmException;
37  
38  import javax.crypto.Cipher;
39  import javax.crypto.CipherInputStream;
40  import javax.crypto.CipherOutputStream;
41  import javax.crypto.KeyGenerator;
42  import javax.crypto.NoSuchPaddingException;
43  import javax.crypto.spec.SecretKeySpec;
44  import javax.xml.parsers.SAXParserFactory;
45  
46  import org.xml.sax.Attributes;
47  import org.xml.sax.InputSource;
48  import org.xml.sax.Locator;
49  import org.xml.sax.SAXException;
50  import org.xml.sax.XMLReader;
51  import org.xml.sax.helpers.DefaultHandler;
52  
53  /**
54   * Represents a configuration expressed through an XML document.
55   * 
56   * @author rlogiacco
57   */
58  public final class XMLConfiguration extends Configuration {
59  
60  	/**
61  	 * Last access time to the underlying byte representation. Used to identify
62  	 * a possible need to update the in-memory representation.
63  	 */
64  	private long access;
65  
66  	/**
67  	 * The location of the byte representation of the configuration.
68  	 */
69  	private URL source;
70  
71  	/**
72  	 * The optional ciphering structure to decode an encrypted configuration.
73  	 */
74  	private Cipher cipher;
75  
76  
77  	/**
78  	 * Constructs a configuration structure from an XML file.
79  	 * 
80  	 * @param source a <code>File</code> object pointing to the underlying
81  	 *        represantation of the configuration.
82  	 * @exception ConfigurationException if the file specified is not a valid
83  	 *            XML file or is no more readable.
84  	 */
85  	public XMLConfiguration(File source) throws ConfigurationException {
86  		try {
87  			this.source = source.toURL();
88  		} catch (MalformedURLException murle) {
89  			throw new ConfigurationException(murle);
90  		}
91  		this.update();
92  	}
93  
94  	/**
95  	 * Constructs a configuration structure from an XML file accessible through
96  	 * a file system dependent string representation of a path.
97  	 * 
98  	 * @param source a <code>String</code> representation of a system
99  	 *        dependant path pointing to a valid configuration representation.
100 	 * @exception ConfigurationException if the file is not a valid XML file or
101 	 *            is no more accessible through the specified path.
102 	 */
103 	public XMLConfiguration(String source) throws ConfigurationException {
104 		this(new File(source));
105 	}
106 
107 	/**
108 	 * Constructs a configuration structure from an XML stream of characters.
109 	 * The <code>isChanged</code> method returns always <code>false</code>
110 	 * 
111 	 * @param source a stream of characters representing a valid XML document.
112 	 * @exception ConfigurationException if the stream doesn't represent a valid
113 	 *            XML document or if an IO error were generated while accessing
114 	 *            the stream.
115 	 */
116 	public XMLConfiguration(URL source) throws ConfigurationException {
117 		this(source, null);
118 	}
119 
120 	/**
121 	 * Constructs a configuration structure from an encrypted XML file. This
122 	 * constructor needs a <code>Cipher</code> reference correctly initialized
123 	 * for the appropriate encoding scheme.
124 	 * 
125 	 * @param source a <code>File</code> object pointing to the underlying
126 	 *        represantation of the configuration.
127 	 * @param cipher an appropriately initialized instance of a
128 	 *        <code>Cipher</code> to be used to decrypt the document.
129 	 * @throws ConfigurationException if the file specified is not a valid XML
130 	 *         file or is no more readable.
131 	 */
132 	public XMLConfiguration(File source, Cipher cipher) throws ConfigurationException {
133 		try {
134 			this.source = source.toURL();
135 		} catch (MalformedURLException murle) {
136 			throw new ConfigurationException(murle);
137 		}
138 		this.cipher = cipher;
139 		this.update();
140 	}
141 
142 	/**
143 	 * Constructs a configuration structure from an encrypted XML file
144 	 * accessible through a file system dependent string representation of a
145 	 * path. This constructor needs a <code>Cipher</code> reference correctly
146 	 * initialized for the appropriate encoding scheme.
147 	 * 
148 	 * @param source a <code>String</code> representation of a system
149 	 *        dependant path pointing to a valid configuration representation.
150 	 * @param cipher an appropriately initialized instance of a
151 	 *        <code>Cipher</code> to be used to decrypt the document.
152 	 * @exception ConfigurationException if the file is not a valid XML file or
153 	 *            is no more accessible through the specified path.
154 	 */
155 	public XMLConfiguration(String source, Cipher cipher) throws ConfigurationException {
156 		this(new File(source), cipher);
157 	}
158 
159 	/**
160 	 * Constructs a configuration structure from an encrypted XML stream of
161 	 * characters. This constructor needs a <code>Cipher</code> reference
162 	 * correctly initialized for the appropriate encoding scheme. The
163 	 * <code>isChanged</code> method returns <code>true</code> only if the
164 	 * <code>update</code> method was never called.
165 	 * 
166 	 * @param source a stream of characters representing a valid XML document.
167 	 * @param cipher an appropriately initialized instance of a
168 	 *        <code>Cipher</code> to be used to decrypt the document.
169 	 * @exception ConfigurationException if the stream doesn't represent a valid
170 	 *            XML document or if an IO error were generated while accessing
171 	 *            the stream.
172 	 */
173 	public XMLConfiguration(URL source, Cipher cipher) throws ConfigurationException {
174 		this.cipher = cipher;
175 		this.source = source;
176 		this.update();
177 	}
178 
179 	/**
180 	 * @see net.smartlab.config.Configuration#isChanged()
181 	 */
182 	public boolean isChanged() throws ConfigurationException {
183 		try {
184 			if (source.openConnection().getLastModified() > access) {
185 				return true;
186 			} else {
187 				return false;
188 			}
189 		} catch (IOException ioe) {
190 			throw new ConfigurationException(ioe);
191 		}
192 	}
193 
194 	/**
195 	 * @see net.smartlab.config.Configuration#update()
196 	 */
197 	public void update() throws ConfigurationException {
198 		try {
199 			this.access = System.currentTimeMillis();
200 			this.parent = null;
201 			this.children.clear();
202 			SAXParserFactory factory = SAXParserFactory.newInstance();
203 			if (cipher != null) {
204 				factory.setValidating(true);
205 				factory.setFeature("http://xml.org/sax/features/external-parameter-entities", true);
206 				factory.newSAXParser().parse(new CipherInputStream(source.openStream(), cipher), new XMLHandler(this));
207 			} else {
208 				factory.setValidating(true);
209 				factory.setFeature("http://xml.org/sax/features/external-parameter-entities", true);
210 				factory.newSAXParser().parse(source.openStream(), new XMLConfiguration.XMLHandler(this));
211 			}
212 		} catch (Exception e) {
213 			throw new ConfigurationException(e);
214 		}
215 	}
216 
217 
218 	/**
219 	 * SAX Handler implementation to provide the in-memory representation of an
220 	 * XML configuration file.
221 	 * 
222 	 * @author Roberto Lo Giacco <rlogiacco@smartlab.net>
223 	 */
224 	static protected class XMLHandler extends DefaultHandler {
225 
226 		/**
227 		 * The owning element also known as the parent element.
228 		 */
229 		protected Element owner;
230 
231 		/**
232 		 * The document locator.
233 		 */
234 		protected Locator locator;
235 
236 		/**
237 		 * The XML stream reader.
238 		 */
239 		protected XMLReader reader;
240 
241 
242 		/**
243 		 * Constructs the handler starting from the configuration root element.
244 		 * 
245 		 * @param owner
246 		 */
247 		public XMLHandler(Configuration owner) {
248 			this.owner = owner;
249 		}
250 
251 		/**
252 		 * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
253 		 */
254 		public void setDocumentLocator(Locator locator) {
255 			this.locator = locator;
256 		}
257 
258 		/**
259 		 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
260 		 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
261 		 */
262 		public void startElement(String namespace, String prefix, String name, Attributes attributes) {
263 			try {
264 				if (owner instanceof Configuration && owner.parent == null) {
265 					((Configuration)owner).init(name, attributes);
266 				} else {
267 					Element child = null;
268 					if (attributes.getIndex(Reference.REFERENCE) > -1) {
269 						child = new Reference((Node)owner, name, attributes.getValue(Reference.REFERENCE));
270 					} else {
271 						child = new Node((Node)owner, name, attributes);
272 					}
273 					((Node)owner).children.add(child);
274 					owner = child;
275 				}
276 			} catch (ClassCastException cce) {
277 				throw new RuntimeException("Reference elements cannot have a body");
278 			}
279 		}
280 
281 		/**
282 		 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
283 		 */
284 		public void characters(char[] chars, int start, int length) {
285 			try {
286 				((Node)owner).setContent(new String(chars, start, length));
287 			} catch (ClassCastException cce) {
288 				throw new RuntimeException("Reference elements cannot have contents");
289 			}
290 		}
291 
292 		/**
293 		 * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
294 		 *      java.lang.String, java.lang.String)
295 		 */
296 		public void endElement(String namespace, String prefix, String name) {
297 			owner = owner.parent;
298 		}
299 
300 		/**
301 		 * Blocks external entity resolution failures.
302 		 * 
303 		 * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String,
304 		 *      java.lang.String)
305 		 */
306 		public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
307 			try {
308 				InputSource source = super.resolveEntity(publicId, systemId);
309 				if (source == null) {
310 					return new InputSource(new StringReader(""));
311 				}
312 				return source;
313 			} catch (IOException ioe) {
314 				throw new SAXException(ioe);
315 			}
316 		}
317 	}
318 
319 
320 	/**
321 	 * TODO documentation
322 	 * 
323 	 * @param plain
324 	 * @param symKey
325 	 * @param ciphered
326 	 * @param algorithm
327 	 * @throws IOException
328 	 * @throws ClassNotFoundException
329 	 * @throws NoSuchAlgorithmException
330 	 * @throws NoSuchPaddingException
331 	 * @throws InvalidKeyException
332 	 */
333 	public static void encrypt(File plain, File symKey, File ciphered, String algorithm) throws IOException,
334 			ClassNotFoundException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
335 		Key key = null;
336 		try {
337 			ObjectInputStream in = new ObjectInputStream(new FileInputStream(symKey));
338 			key = (Key)in.readObject();
339 		} catch (IOException ioe) {
340 			KeyGenerator generator = KeyGenerator.getInstance(algorithm);
341 			key = generator.generateKey();
342 			ObjectOutputStream out = new ObjectOutputStream(new java.io.FileOutputStream(symKey));
343 			out.writeObject(key);
344 			out.close();
345 		}
346 		Cipher cipher = Cipher.getInstance(algorithm);
347 		cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getEncoded(), algorithm));
348 		FileInputStream in = new FileInputStream(plain);
349 		CipherOutputStream out = new CipherOutputStream(new FileOutputStream(ciphered), cipher);
350 		byte[] buffer = new byte[4096];
351 		for (int read = in.read(buffer); read > -1; read = in.read(buffer)) {
352 			out.write(buffer, 0, read);
353 		}
354 		out.close();
355 	}
356 }