View Javadoc

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