001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.datadictionary;
017
018 import no.geosoft.cc.io.FileListener;
019 import no.geosoft.cc.io.FileMonitor;
020 import org.apache.commons.lang.StringUtils;
021 import org.apache.commons.logging.Log;
022 import org.apache.commons.logging.LogFactory;
023 import org.kuali.rice.core.api.config.property.ConfigurationService;
024 import org.kuali.rice.krad.service.KRADServiceLocator;
025 import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
026 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
027 import org.springframework.core.io.FileSystemResource;
028 import org.springframework.core.io.InputStreamResource;
029 import org.springframework.core.io.Resource;
030
031 import java.io.File;
032 import java.io.InputStream;
033 import java.net.URL;
034 import java.util.ArrayList;
035 import java.util.List;
036
037
038 /**
039 * Extends the DataDictionary to add reloading of changed dictionary files
040 * without a restart of the web container
041 *
042 * <p>
043 * To use modify the "dataDictionaryService" spring definition
044 * (KRADSpringBeans.xml) and change the constructor arg bean class from
045 * "org.kuali.rice.krad.datadictionary.DataDictionary" to
046 * "ReloadingDataDictionary"
047 * </p>
048 *
049 * <p>
050 * NOTE: For Development Purposes Only!
051 * </p>
052 *
053 * @author Kuali Rice Team (rice.collab@kuali.org)
054 */
055 public class ReloadingDataDictionary extends DataDictionary implements FileListener, URLMonitor.URLContentChangedListener {
056 private static final Log LOG = LogFactory.getLog(DataDictionary.class);
057
058 private static final String CLASS_DIR_CONFIG_PARM = "reload.data.dictionary.classes.dir";
059 private static final String SOURCE_DIR_CONFIG_PARM = "reload.data.dictionary.source.dir";
060 private static final String INTERVAL_CONFIG_PARM = "reload.data.dictionary.interval";
061
062 private URLMonitor dictionaryUrlMonitor;
063
064
065 public ReloadingDataDictionary() {
066 super();
067 }
068
069 /**
070 * After dictionary has been loaded, determine the source files and add them
071 * to the monitor
072 *
073 * @see org.kuali.rice.krad.datadictionary.DataDictionary#parseDataDictionaryConfigurationFiles(boolean)
074 */
075 @Override
076 public void parseDataDictionaryConfigurationFiles(boolean allowConcurrentValidation) {
077 ConfigurationService configurationService = KRADServiceLocator.getKualiConfigurationService();
078
079 // class directory part of the path that should be replaced
080 String classesDir = configurationService.getPropertyValueAsString(CLASS_DIR_CONFIG_PARM);
081
082 // source directory where dictionary files are found
083 String sourceDir = configurationService.getPropertyValueAsString(SOURCE_DIR_CONFIG_PARM);
084
085 // interval to poll for changes in milliseconds
086 int reloadInterval = Integer.parseInt(configurationService.getPropertyValueAsString(INTERVAL_CONFIG_PARM));
087
088 FileMonitor dictionaryFileMonitor = new FileMonitor(reloadInterval);
089
090 dictionaryUrlMonitor = new URLMonitor(reloadInterval);
091 dictionaryUrlMonitor.addListener(this);
092
093 // need to copy the configFileLocations list here because it gets
094 // cleared out after processing by super
095 List<String> configLocations = new ArrayList<String>(configFileLocations);
096
097 super.parseDataDictionaryConfigurationFiles(allowConcurrentValidation);
098 for (String configLocation : configLocations) {
099 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 }