View Javadoc

1   /*
2    * $HeadURL: $
3    * $Date: $
4    * $Revision: $
5    * $Author: $
6    * 
7    * Copyright (c) 2005 MindTree Consulting Ltd. 
8    * 
9    * This file is part of Insight.
10   * 
11   * Insight is free software: you can redistribute it and/or modify it under the 
12   * terms of the GNU General Public License as published by the Free Software 
13   * Foundation, either version 3 of the License, or (at your option) any later 
14   * version.
15   * 
16   * Insight is distributed in the hope that it will be useful, but 
17   * WITHOUT ANY WARRANTY; without even the implied warranty of 
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
19   * Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public License along with 
22   * Insight.  If not, see <http://www.gnu.org/licenses/>.
23   */
24  
25  package com.mindtree.techworks.insight.preferences.xmlpersistence;
26  
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.net.URL;
32  import java.nio.channels.FileChannel;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Iterator;
36  import java.util.List;
37  
38  import javax.xml.parsers.ParserConfigurationException;
39  import javax.xml.parsers.SAXParser;
40  import javax.xml.parsers.SAXParserFactory;
41  
42  import org.xml.sax.InputSource;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.SAXParseException;
45  import org.xml.sax.XMLReader;
46  import org.xml.sax.helpers.DefaultHandler;
47  
48  import com.mindtree.techworks.insight.InsightConstants;
49  import com.mindtree.techworks.insight.preferences.PreferenceDataHandler;
50  import com.mindtree.techworks.insight.preferences.PreferenceHandlerInstantiationException;
51  import com.mindtree.techworks.insight.preferences.PreferenceHandlerStoreException;
52  import com.mindtree.techworks.insight.preferences.model.Preference;
53  
54  
55  /**
56   * <p>
57   * This is an implementation of the PreferenceDataHandler. It populates the
58   * preferences stored in an XML file following a schema. The schema is located
59   * within the classpath at the location
60   * <code>com.mindtree.techworks.insight.preferences.xmlpersistence.res.InsightPreferences.xsd</code>.
61   * <br>
62   * For more details on the schema see
63   * {@link com.mindtree.techworks.insight.preferences.xmlpersistence.XMLPreferenceDataReader XMLPreferenceDataReader}.
64   * </p>
65   * 
66   * @see com.mindtree.techworks.insight.preferences.PreferenceDataHandler
67   *      PreferenceDataHandler
68   * @author Bindul Bhowmik
69   * @version $Revision: 27 $ $Date: 2007-12-16 04:58:03 -0700 (Sun, 16 Dec 2007) $
70   */
71  public class XMLPreferenceDataHandler implements PreferenceDataHandler {
72  
73  	/**
74  	 * The location of the schema for preferences.
75  	 */
76  	private static final String SCHEMA_LOCATION = "com/mindtree/techworks/insight/preferences/xmlpersistence/res/InsightPreferences.xsd";
77  
78  	/**
79  	 * The target namespace for the Schema
80  	 */
81  	private static final String TARGET_NAMESPACE = "http://mindtree.com/techworks/insight/Preferences";
82  
83  	/**
84  	 * <b>From Apache Xerces Documentation </b>
85  	 * 
86  	 * <pre>
87  	 * 
88  	 *  
89  	 *   
90  	 *    
91  	 *     True:  	Validate the document and report validity errors.
92  	 *     False:  	Do not report validity errors.
93  	 *     Default:	false
94  	 *     Access:	(parsing) read-only; (not parsing) read-write;
95  	 *     Note:  	If this feature is set to true, the document must specify a 
96  	 *     			grammar. By default, validation will occur against DTD. For 
97  	 *     			more information, please, refer to the FAQ. If this feature is 
98  	 *     			set to false, and document specifies a grammar that grammar 
99  	 *     			might be parsed but no validation of the document contents will 
100 	 *     			be performed.
101 	 *     See:  	http://apache.org/xml/features/validation/dynamic
102 	 *     See:  	http://xml.org/sax/features/namespaces
103 	 *     See:  	http://apache.org/xml/features/nonvalidating/load-external-dtd
104 	 *     
105 	 *    
106 	 *   
107 	 *  
108 	 * </pre>
109 	 */
110 	private static final String FEATURE_VALIDATION = "http://xml.org/sax/features/validation";
111 
112 	/**
113 	 * <b>From Apache Xerces Documentation </b>
114 	 * 
115 	 * <pre>
116 	 * 
117 	 *  
118 	 *   
119 	 *    
120 	 *     True:  	Turn on XML Schema validation by inserting XML Schema validator 
121 	 *     			in the pipeline.
122 	 *     False:  	Do not report validation errors against XML Schema.
123 	 *     Default:	false
124 	 *     Access:	(parsing) read-only; (not parsing) read-write;
125 	 *     Note:  	Validation errors will only be reported if validation feature is 
126 	 *     			set to true. For more information, please, refer to the FAQ
127 	 *     See:  	http://xml.org/sax/features/validation
128 	 *     See:  	http://apache.org/xml/features/validation/dynamic
129 	 *     See:  	http://xml.org/sax/features/namespaces
130 	 *     
131 	 *    
132 	 *   
133 	 *  
134 	 * </pre>
135 	 * 
136 	 * @see XMLPreferenceDataHandler#FEATURE_VALIDATION
137 	 *      http://xml.org/sax/features/validation
138 	 */
139 	private static final String FEATURE_VALIDATION_SCHEMA = "http://apache.org/xml/features/validation/schema";
140 
141 	/**
142 	 * <b>From Apache Xerces Documentation </b>
143 	 * 
144 	 * <pre>
145 	 * 
146 	 *  
147 	 *   
148 	 *    
149 	 *     Desc:	The XML Schema Recommendation explicitly states that the 
150 	 *     			inclusion of schemaLocation/noNamespaceSchemaLocation attributes 
151 	 *     			is only a hint; it does not mandate that these attributes must 
152 	 *     			be used to locate schemas. Similar situation happens to 
153 	 *     			&lt;import&gt; element in schema documents. This property allows 
154 	 *     			the user to specify a list of schemas to use. If the 
155 	 *     			targetNamespace of a schema (specified using this property) 
156 	 *     			matches the targetNamespace of a schema occurring in the 
157 	 *     			instance document in schemaLocation attribute, or if the 
158 	 *     			targetNamespace matches the namespace attribute of &lt;import&gt; 
159 	 *     			element, the schema specified by the user using this property 
160 	 *     			will be used (i.e., the schemaLocation attribute in the instance 
161 	 *     			document or on the &lt;import&gt; element will be effectively 
162 	 *     			ignored).
163 	 *     Type:  	java.lang.String
164 	 *     Access:  read-write
165 	 *     Note:  	The syntax is the same as for schemaLocation attributes in 
166 	 *     			instance documents: e.g, &quot;http://www.example.com file_name.xsd&quot;. 
167 	 *     			The user can specify more than one XML Schema in the list.
168 	 *     
169 	 *    
170 	 *   
171 	 *  
172 	 * </pre>
173 	 */
174 	private static final String PROPERTY_SCHEMA_EXTLOC = "http://apache.org/xml/properties/schema/external-schemaLocation";
175 
176 	/**
177 	 * Lexical handler property id
178 	 * (http://xml.org/sax/properties/lexical-handler).
179 	 */
180 	protected static final String LEXICAL_HANDLER_PROPERTY_ID = "http://xml.org/sax/properties/lexical-handler";
181 
182 	/**
183 	 * Relative path of the config file
184 	 */
185 	public static final String XML_PROP_FILE_URI = "/config/insight-preferences.xml";
186 
187 	/**
188 	 * Location of the preferences file
189 	 */
190 	private String preferenceFileLocation;
191 
192 	/**
193 	 * Stores the location of the schema.
194 	 * <p>
195 	 * This variable should have been a local variable of the validateXMLFile()
196 	 * method, but it needs to be accessed by the inner class, hench it is made
197 	 * a class field.
198 	 * </p>
199 	 */
200 	protected String schemaFilePath;
201 
202 	/**
203 	 * Default constructor.
204 	 * 
205 	 * @throws PreferenceHandlerInstantiationException When the Handler cannot
206 	 *             be instantiated because the XML is not valid or is not found.
207 	 */
208 	public XMLPreferenceDataHandler ()
209 			throws PreferenceHandlerInstantiationException {
210 
211 		preferenceFileLocation = System
212 				.getProperty(InsightConstants.INSIGHT_HOME)
213 				+ XML_PROP_FILE_URI;
214 
215 		File prefFile = new File(preferenceFileLocation);
216 		if (!prefFile.isFile() || !prefFile.canRead() || !prefFile.canWrite()) {
217 			throw new PreferenceHandlerInstantiationException(
218 					"Preference File Cannot be read, or is not writable");
219 		}
220 
221 		XMLPreferenceDataCache dataCache = XMLPreferenceDataCache.getInstance();
222 
223 		// Validate the XML File
224 		if (!dataCache.isXMLValidated()) {
225 			boolean fileValidity = validateXMLFile();
226 			if (false == fileValidity) {
227 				throw new PreferenceHandlerInstantiationException(
228 						"Cannot get valid xml");
229 			}
230 			dataCache.setXMLValidated(true);
231 		}
232 	}
233 
234 	/**
235 	 * <p>
236 	 * Returns a list of PreferenceInfo objects.
237 	 * </p>
238 	 * <p>
239 	 * It first checks if the preferences have been loaded onto the cache or
240 	 * not. If not then loads the preferences on the cache and then returns the
241 	 * preference info.
242 	 * </p>
243 	 * 
244 	 * @see com.mindtree.techworks.insight.preferences.PreferenceDataHandler#getPreferenceNameList()
245 	 */
246 	public Collection getPreferenceNameList () {
247 
248 		XMLPreferenceDataCache dataCache = XMLPreferenceDataCache.getInstance();
249 		if (!dataCache.isXMLLoaded()) {
250 			loadPreferencesIntoCache();
251 		}
252 
253 		ArrayList preferenceInfo = new ArrayList();
254 
255 		for (Iterator preferencesIterator = dataCache.iteratePreferences(); preferencesIterator
256 				.hasNext();) {
257 			Preference preference = (Preference) preferencesIterator.next();
258 			preferenceInfo.add(preference.getPreferenceInfo());
259 		}
260 
261 		return preferenceInfo;
262 	}
263 
264 	/**
265 	 * <p>
266 	 * Returns a preference for the id passed, or null if no preference is found
267 	 * for the id.
268 	 * </p>
269 	 * <p>
270 	 * It first checks if the preferences have been loaded onto the cache or
271 	 * not. If not then loads the preferences on the cache and then returns the
272 	 * preference.
273 	 * </p>
274 	 * 
275 	 * @see com.mindtree.techworks.insight.preferences.PreferenceDataHandler#getPreference(java.lang.String)
276 	 */
277 	public Preference getPreference (String preferenceId) {
278 
279 		XMLPreferenceDataCache dataCache = XMLPreferenceDataCache.getInstance();
280 		if (!dataCache.isXMLLoaded()) {
281 			loadPreferencesIntoCache();
282 		}
283 
284 		return dataCache.getPreference(preferenceId);
285 	}
286 
287 	/**
288 	 * Saves the given preferences
289 	 * 
290 	 * @throws PreferenceHandlerStoreException
291 	 * @see com.mindtree.techworks.insight.preferences.PreferenceDataHandler#savePreferences(java.util.List)
292 	 */
293 	public void savePreferences (List preferences)
294 			throws PreferenceHandlerStoreException {
295 
296 		String tempFilePath;
297 		try {
298 			File tempFile = File.createTempFile("insight-preference", ".xml");
299 			tempFile.deleteOnExit();
300 			tempFilePath = tempFile.getAbsolutePath();
301 		} catch (IOException e) {
302 			throw new PreferenceHandlerStoreException(e);
303 		}
304 		
305 		XMLDOMPreferenceDataWriter writer = new XMLDOMPreferenceDataWriter(
306 				preferenceFileLocation);
307 		writer.writePreferences(preferences, tempFilePath);
308 
309 		// Copy the file
310 		File origFile = new File(preferenceFileLocation);
311 		origFile.delete();
312 		origFile = new File(preferenceFileLocation);
313 		File sourceFile = new File(tempFilePath);
314 		try {
315 			copyFile(sourceFile, origFile);
316 		} catch (IOException e) {
317 			throw new PreferenceHandlerStoreException(e);
318 		}
319 		sourceFile.delete();
320 	}
321 
322 	/**
323 	 * Returns all the preferences.
324 	 * 
325 	 * @see com.mindtree.techworks.insight.preferences.PreferenceDataHandler#getAllPreferences()
326 	 */
327 	public Collection getAllPreferences () {
328 
329 		XMLPreferenceDataCache dataCache = XMLPreferenceDataCache.getInstance();
330 		if (!dataCache.isXMLLoaded()) {
331 			loadPreferencesIntoCache();
332 		}
333 
334 		return dataCache.getAllPreferences();
335 	}
336 
337 	/**
338 	 * Validates the preferences XML file against the Schema. This is done only
339 	 * once at the init of the instance.
340 	 * 
341 	 * @return <code>true</code> if the xml is valid or <code>false</code>.
342 	 */
343 	private boolean validateXMLFile () {
344 
345 		// Get the system location of the Schema
346 		URL schemaLocation = Thread.currentThread().getContextClassLoader().getResource(
347 				SCHEMA_LOCATION);
348 		schemaFilePath = schemaLocation.getFile();
349 		String schemaExtLoc = TARGET_NAMESPACE + " " + schemaFilePath;
350 
351 		// File Input Stream that will used to get the preference xml.
352 		FileInputStream fis = null;
353 
354 		// Parser factory for getting the SAX parser
355 		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
356 		saxParserFactory.setNamespaceAware(true);
357 
358 		SAXParser parser = null;
359 
360 		try {
361 			parser = saxParserFactory.newSAXParser();
362 			XMLReader xmlReader = parser.getXMLReader();
363 
364 			// Set the features and properties of the Parser
365 			xmlReader.setFeature(FEATURE_VALIDATION, true);
366 			xmlReader.setFeature(FEATURE_VALIDATION_SCHEMA, true);
367 			xmlReader.setProperty(PROPERTY_SCHEMA_EXTLOC, schemaExtLoc);
368 
369 			/*
370 			 * Inner class to throw validation errors. It extends from the
371 			 * org.xml.sax.helepers.DefaultHandler
372 			 */
373 			DefaultHandler validationHandler = (new DefaultHandler() {
374 
375 				/**
376 				 * Warning from Validation Handler
377 				 * 
378 				 * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
379 				 */
380 				public void warning (SAXParseException e) throws SAXException {
381 
382 					throw e;
383 				}
384 
385 				/**
386 				 * Error from Validation Handler
387 				 * 
388 				 * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
389 				 */
390 				public void error (SAXParseException e) throws SAXException {
391 
392 					throw e;
393 				}
394 
395 				/**
396 				 * Fatal Error from Validation Handler
397 				 * 
398 				 * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
399 				 */
400 				public void fatalError (SAXParseException e)
401 						throws SAXException {
402 
403 					throw e;
404 				}
405 
406 				/**
407 				 * Entity resolver to be used for XSD file!
408 				 * 
409 				 * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String,
410 				 *      java.lang.String)
411 				 */
412 				public InputSource resolveEntity (String publicId,
413 						String systemId) throws SAXException {
414 
415 					if (systemId.endsWith(schemaFilePath)) {
416 						InputSource schemaInputSource = new InputSource(this
417 								.getClass().getClassLoader()
418 								.getResourceAsStream(SCHEMA_LOCATION));
419 						return schemaInputSource;
420 					}
421 					try {
422 						return super.resolveEntity(publicId, systemId);
423 					} catch (Exception e) {
424 						throw new SAXException("Exception occured resolving entity", e);
425 					}
426 				}
427 
428 
429 			});
430 
431 			fis = new FileInputStream(preferenceFileLocation);
432 			parser.parse(new InputSource(fis), validationHandler);
433 
434 		} catch (ParserConfigurationException e) {
435 			e.printStackTrace();
436 			return false;
437 		} catch (SAXException e) {
438 			e.printStackTrace();
439 			return false;
440 		} catch (IOException e) {
441 			e.printStackTrace();
442 			return false;
443 		} finally {
444 			try {
445 				fis.close();
446 			} catch (IOException e) {
447 				e.printStackTrace();
448 			}
449 		}
450 		return true;
451 	}
452 
453 	/**
454 	 * Loads the Preferences from the XML file into the cache.
455 	 */
456 	private void loadPreferencesIntoCache () {
457 
458 		// File Input Stream that will used to get the preference xml.
459 		FileInputStream fis = null;
460 
461 		// Parser factory for getting the SAX parser
462 		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
463 		saxParserFactory.setNamespaceAware(true);
464 
465 		SAXParser parser = null;
466 
467 		try {
468 			parser = saxParserFactory.newSAXParser();
469 
470 			DefaultHandler validationHandler = new XMLPreferenceDataReader();
471 
472 			fis = new FileInputStream(preferenceFileLocation);
473 			parser.parse(new InputSource(fis), validationHandler);
474 
475 		} catch (ParserConfigurationException e) {
476 			e.printStackTrace();
477 		} catch (SAXException e) {
478 			e.printStackTrace();
479 		} catch (IOException e) {
480 			e.printStackTrace();
481 		} finally {
482 			try {
483 				fis.close();
484 			} catch (IOException e) {
485 				e.printStackTrace();
486 			}
487 		}
488 
489 		XMLPreferenceDataCache cache = XMLPreferenceDataCache.getInstance();
490 		cache.setXMLLoaded(true);
491 	}
492 
493 	/**
494 	 * Copies the in file to out file
495 	 * 
496 	 * @param in Location of Source File
497 	 * @param out Location of Destination File
498 	 * @throws IOException
499 	 * @throws IOException Exception if Copy Fails
500 	 */
501 	private void copyFile (File in, File out) throws IOException {
502 
503 		FileChannel sourceChannel = new FileInputStream(in).getChannel();
504 		FileChannel destinationChannel = new FileOutputStream(out).getChannel();
505 		sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
506 		sourceChannel.close();
507 		destinationChannel.close();
508 	}
509 }