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 }