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 }