View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.rice.krad.datadictionary;
17  
18  import no.geosoft.cc.io.FileListener;
19  import no.geosoft.cc.io.FileMonitor;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.kuali.rice.core.api.config.property.ConfigurationService;
24  import org.kuali.rice.krad.service.KRADServiceLocator;
25  import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
26  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
27  import org.springframework.core.io.FileSystemResource;
28  import org.springframework.core.io.InputStreamResource;
29  import org.springframework.core.io.Resource;
30  
31  import java.io.File;
32  import java.io.InputStream;
33  import java.net.URL;
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  
38  /**
39   * Extends the DataDictionary to add reloading of changed dictionary files
40   * without a restart of the web container
41   * 
42   * <p>
43   * To use modify the "dataDictionaryService" spring definition
44   * (KRADSpringBeans.xml) and change the constructor arg bean class from
45   * "org.kuali.rice.krad.datadictionary.DataDictionary" to
46   * "ReloadingDataDictionary"
47   * </p>
48   * 
49   * <p>
50   * NOTE: For Development Purposes Only!
51   * </p>
52   * 
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   */
55  public class ReloadingDataDictionary extends DataDictionary implements FileListener, URLMonitor.URLContentChangedListener {
56  	private static final Log LOG = LogFactory.getLog(DataDictionary.class);
57  
58  	private static final String CLASS_DIR_CONFIG_PARM = "reload.data.dictionary.classes.dir";
59  	private static final String SOURCE_DIR_CONFIG_PARM = "reload.data.dictionary.source.dir";
60  	private static final String INTERVAL_CONFIG_PARM = "reload.data.dictionary.interval";
61  
62      private URLMonitor dictionaryUrlMonitor;
63  
64  
65  	public ReloadingDataDictionary() {
66  		super();
67  	}
68  
69  	/**
70  	 * After dictionary has been loaded, determine the source files and add them
71  	 * to the monitor
72  	 * 
73  	 * @see org.kuali.rice.krad.datadictionary.DataDictionary#parseDataDictionaryConfigurationFiles(boolean)
74  	 */
75  	@Override
76  	public void parseDataDictionaryConfigurationFiles(boolean allowConcurrentValidation) {
77  		ConfigurationService configurationService = KRADServiceLocator.getKualiConfigurationService();
78  
79  		// class directory part of the path that should be replaced
80  		String classesDir = configurationService.getPropertyValueAsString(CLASS_DIR_CONFIG_PARM);
81  
82  		// source directory where dictionary files are found
83  		String sourceDir = configurationService.getPropertyValueAsString(SOURCE_DIR_CONFIG_PARM);
84  
85  		// interval to poll for changes in milliseconds
86  		int reloadInterval = Integer.parseInt(configurationService.getPropertyValueAsString(INTERVAL_CONFIG_PARM));
87  
88  		FileMonitor dictionaryFileMonitor = new FileMonitor(reloadInterval);
89  
90          dictionaryUrlMonitor = new URLMonitor(reloadInterval);
91          dictionaryUrlMonitor.addListener(this);
92  
93  		// need to copy the configFileLocations list here because it gets
94  		// cleared out after processing by super
95  		List<String> configLocations = new ArrayList<String>(configFileLocations);
96  
97  		super.parseDataDictionaryConfigurationFiles(allowConcurrentValidation);
98  		for (String configLocation : configLocations) {
99  			Resource classFileResource = getFileResource(configLocation);
100 			try {
101                 if (classFileResource.getURI().toString().startsWith("jar:")) {
102                     LOG.debug("Monitoring dictionary file at URI: " + classFileResource.getURI().toString());
103                     dictionaryUrlMonitor.addURI(classFileResource.getURL());
104                 } else {
105                     String filePathClassesDir = classFileResource.getFile().getAbsolutePath();
106                     String sourceFilePath = StringUtils.replace(filePathClassesDir, classesDir, sourceDir);
107                     File dictionaryFile = new File(filePathClassesDir);
108                     if (dictionaryFile.exists()) {
109                         LOG.debug("Monitoring dictionary file: " + dictionaryFile.getName());
110                         dictionaryFileMonitor.addFile(dictionaryFile);
111                     }
112                 }
113 			}
114 			catch (Exception e) {
115 				LOG.info("Exception in picking up dictionary file for monitoring:  " + e.getMessage(), e);
116 			}
117 		}
118 
119 		// add the dictionary as a listener for file changes
120 		dictionaryFileMonitor.addListener(this);
121 	}
122 
123 	/**
124 	 * Call back when a dictionary file is changed. Calls the spring bean reader
125 	 * to reload the file (which will override beans as necessary and destroy
126 	 * singletons) and runs the indexer
127 	 * 
128 	 * @see no.geosoft.cc.io.FileListener#fileChanged(java.io.File)
129 	 */
130 	@Override
131 	public void fileChanged(File file) {
132 		LOG.info("reloading dictionary configuration for " + file.getName());
133 		try {
134 			Resource resource = new FileSystemResource(file);
135 			xmlReader.loadBeanDefinitions(resource);
136 
137             UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor();
138             factoryPostProcessor.postProcessBeanFactory(ddBeans);
139 
140 			// re-index
141 			ddIndex.run();
142 		}
143 		catch (Exception e) {
144 			LOG.info("Exception in dictionary hot deploy: " + e.getMessage(), e);
145 		}
146 	}
147 
148     public void urlContentChanged(final URL url) {
149         LOG.info("reloading dictionary configuration for " + url.toString());
150         try {
151             InputStream urlStream = url.openStream();
152             InputStreamResource resource = new InputStreamResource(urlStream);
153 
154             int originalValidationMode = xmlReader.getValidationMode();
155             xmlReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
156             xmlReader.loadBeanDefinitions(resource);
157             xmlReader.setValidationMode(originalValidationMode);
158 
159             UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor();
160             factoryPostProcessor.postProcessBeanFactory(ddBeans);
161 
162             // re-index
163             ddIndex.run();
164         }
165         catch (Exception e) {
166             LOG.info("Exception in dictionary hot deploy: " + e.getMessage(), e);
167         }
168     }
169 }