/** * The contents of this file are subject to the OpenMRS Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.messagesource.impl; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.messagesource.MutableMessageSource; import org.openmrs.messagesource.PresentationMessage; import org.openmrs.util.LocaleUtility; import org.openmrs.util.OpenmrsUtil; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.core.io.Resource; /** * ResourceBundleMessageSource extends ReloadableResourceBundleMessageSource to provide the * additional features of a MutableMessageSource. */ public class MutableResourceBundleMessageSource extends ReloadableResourceBundleMessageSource implements MutableMessageSource, ApplicationContextAware { private static final String PROPERTIES_FILE_COMMENT = "OpenMRS Application Messages"; private Log log = LogFactory.getLog(getClass()); private ApplicationContext applicationContext; /** * Local reference to basenames used to search for properties files. */ private String[] basenames = new String[0]; private int cacheMilliseconds = -1; private long lastCached = System.currentTimeMillis(); /** Cached list of available locales. */ private Collection<Locale> locales; /** * @see org.openmrs.messagesource.MessageSourceService#getLocales() */ public Collection<Locale> getLocales() { long now = System.currentTimeMillis(); if (locales == null || cacheMilliseconds <= 0 || now - cacheMilliseconds > lastCached) { locales = findLocales(); lastCached = now; } return locales; } @Override public void setCacheSeconds(int cacheSeconds) { this.cacheMilliseconds = cacheSeconds * 1000; super.setCacheSeconds(cacheSeconds); } /** * This method looks at the current property files and deduces what locales are available from * those * * @see #getLocales() * @see #findPropertiesFiles() */ private Collection<Locale> findLocales() { Collection<Locale> foundLocales = new HashSet<Locale>(); for (File propertiesFile : findPropertiesFiles()) { String filename = propertiesFile.getName(); Locale parsedLocale = parseLocaleFrom(filename); foundLocales.add(parsedLocale); } if (foundLocales.size() == 0) { log.warn("no locales found."); } return foundLocales; } /** * Utility method for deriving a locale from a filename, presumed to have an embedded locale * specification near the end. For instance messages_it.properties if the filename is * messages.properties, the Locale is presumed to be the default set for Java * * @param filename the name to parse * @return Locale derived from the given string */ private Locale parseLocaleFrom(String filename) { Locale parsedLocale = null; // trim off leading basename for (String basename : basenames) { File basefilename = new File(basename); basename = basefilename.getPath(); int indexOfLastPart = basename.lastIndexOf(File.separatorChar) + 1; if (indexOfLastPart > 0) basename = basename.substring(indexOfLastPart); if (filename.startsWith(basename)) { filename = filename.substring(basename.length()); } } // trim off extension String localespec = filename.substring(0, filename.indexOf('.')); if (localespec.equals("")) { parsedLocale = Locale.getDefault(); } else { localespec = localespec.substring(1); // trim off leading '_' parsedLocale = LocaleUtility.fromSpecification(localespec); } return parsedLocale; } /** * Presumes to append the messages to a message.properties file which is already being monitored * by the super ReloadableResourceBundleMessageSource. This is a blind, trusting hack. * * @see org.openmrs.messagesource.MutableMessageSource#publishProperties(java.util.Properties, * java.lang.String, java.lang.String, java.lang.String, java.lang.String) * @deprecated use {@linkplain #merge(MutableMessageSource, boolean)} */ @Deprecated public void publishProperties(Properties props, String locale, String namespace, String name, String version) { String filePrefix = (namespace.length() > 0) ? (namespace + "_") : ""; String propertiesPath = "/WEB-INF/" + filePrefix + "messages" + locale + ".properties"; Resource propertiesResource = applicationContext.getResource(propertiesPath); try { File propertiesFile = propertiesResource.getFile(); if (!propertiesFile.exists()) propertiesFile.createNewFile(); // append the properties to the appropriate messages file OpenmrsUtil.storeProperties(props, propertiesFile, namespace + ": " + name + " v" + version); } catch (Exception ex) { log.error("Error creating new properties file"); } } /** * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * Returns all available messages. * * @see org.openmrs.messagesource.MessageSourceService#getPresentations() */ public Collection<PresentationMessage> getPresentations() { Collection<PresentationMessage> presentations = new Vector<PresentationMessage>(); for (File propertiesFile : findPropertiesFiles()) { Locale currentLocale = parseLocaleFrom(propertiesFile.getName()); Properties props = new Properties(); try { OpenmrsUtil.loadProperties(props, propertiesFile); for (Map.Entry<Object, Object> property : props.entrySet()) { presentations.add(new PresentationMessage(property.getKey().toString(), currentLocale, property .getValue().toString(), "")); } } catch (Exception e) { // skip over errors in loading a single file log.error("Unable to load properties from file: " + propertiesFile.getAbsolutePath(), e); } } return presentations; } /** * Override to obtain a local reference to the basenames. * * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setBasename(java.lang.String) */ @Override public void setBasename(String basename) { super.setBasename(basename); this.basenames = new String[] { basename }; } /** * Override to obtain a local reference to the basenames. * * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setBasenames(java.lang.String[]) */ @Override public void setBasenames(String[] basenames) { super.setBasenames(basenames); this.basenames = basenames; } /** * @see org.openmrs.messagesource.MutableMessageSource#addPresentation(org.openmrs.messagesource.PresentationMessage) */ public void addPresentation(PresentationMessage message) { File propertyFile = findPropertiesFileFor(message.getCode()); if (propertyFile != null) { Properties props = new Properties(); try { OpenmrsUtil.loadProperties(props, propertyFile); props.setProperty(message.getCode(), message.getMessage()); OpenmrsUtil.storeProperties(props, propertyFile, "OpenMRS Application Messages"); } catch (Exception e) { log.error("Error generated", e); } } } /** * @see org.openmrs.messagesource.MutableMessageSource#removePresentation(org.openmrs.messagesource.PresentationMessage) */ public void removePresentation(PresentationMessage message) { File propertyFile = findPropertiesFileFor(message.getCode()); if (propertyFile != null) { Properties props = new Properties(); try { OpenmrsUtil.loadProperties(props, propertyFile); props.remove(message.getCode()); OpenmrsUtil.storeProperties(props, propertyFile, PROPERTIES_FILE_COMMENT); } catch (Exception e) { log.error("Error generated", e); } } } /** * Convenience method to scan the available properties files, looking for the one that has a * definition for the given code. * * @param code * @return the file which defines the code, or null if not found */ private File findPropertiesFileFor(String code) { Properties props = new Properties(); File foundFile = null; for (File propertiesFile : findPropertiesFiles()) { props.clear(); try { OpenmrsUtil.loadProperties(props, propertiesFile); } catch (Exception e) { log.error("Error generated", e); } if (props.containsKey(code)) { foundFile = propertiesFile; break; } } return foundFile; } /** * Searches the filesystem for message properties files. ABKTODO: consider caching this, rather * than searching every time * * @return collection of property file names */ private Collection<File> findPropertiesFiles() { Collection<File> propertiesFiles = new Vector<File>(); try { for (String basename : basenames) { File basefilename = new File(basename); basename = basefilename.getPath(); int nameIndex = basename.lastIndexOf(File.separatorChar) + 1; String basedir = (nameIndex > 0) ? basename.substring(0, nameIndex) : ""; String namePrefix = basename.substring(nameIndex); Resource propertiesDir = applicationContext.getResource(basedir); boolean filesFound = false; for (File possibleFile : propertiesDir.getFile().listFiles()) { if (possibleFile.getName().startsWith(namePrefix) && possibleFile.getName().endsWith(".properties")) { propertiesFiles.add(possibleFile); filesFound = true; } } if (log.isDebugEnabled() && !filesFound) { log.debug("No messages for basename " + basename); } } } catch (IOException e) { log.error("Error generated", e); } if (log.isWarnEnabled() && (propertiesFiles.size() == 0)) { log.warn("No properties files found."); } return propertiesFiles; } /** * @see org.openmrs.messagesource.MutableMessageSource#merge(MutableMessageSource, boolean) */ public void merge(MutableMessageSource fromSource, boolean overwrite) { // collect all existing properties Collection<File> propertiesFiles = findPropertiesFiles(); Map<Locale, List<File>> localeToFilesMap = new HashMap<Locale, List<File>>(); Map<File, Properties> fileToPropertiesMap = new HashMap<File, Properties>(); for (File propertiesFile : propertiesFiles) { Properties props = new Properties(); Locale propsLocale = parseLocaleFrom(propertiesFile.getName()); List<File> propList = localeToFilesMap.get(propsLocale); if (propList == null) { propList = new ArrayList<File>(); localeToFilesMap.put(propsLocale, propList); } propList.add(propertiesFile); try { OpenmrsUtil.loadProperties(props, propertiesFile); fileToPropertiesMap.put(propertiesFile, props); } catch (Exception e) { // skip over errors in loading a single file log.error("Unable to load properties from file: " + propertiesFile.getAbsolutePath(), e); } } // merge in the new properties for (PresentationMessage message : fromSource.getPresentations()) { Locale messageLocale = message.getLocale(); List<File> filelist = localeToFilesMap.get(messageLocale); if (filelist != null) { Properties propertyDestination = null; boolean propExists = false; for (File propertiesFile : filelist) { Properties possibleDestination = fileToPropertiesMap.get(propertiesFile); if (possibleDestination.containsKey(message.getCode())) { propertyDestination = possibleDestination; propExists = true; break; } else if (propertyDestination == null) propertyDestination = possibleDestination; } if ((propExists && overwrite) || !propExists) { propertyDestination.put(message.getCode(), message.getMessage()); } } else { // no properties files for this locale, create one File newPropertiesFile = new File(basenames[0] + "_" + messageLocale.toString() + ".properties"); Properties newProperties = new Properties(); fileToPropertiesMap.put(newPropertiesFile, newProperties); newProperties.put(message.getCode(), message.getMessage()); List<File> newFilelist = new ArrayList<File>(); newFilelist.add(newPropertiesFile); localeToFilesMap.put(messageLocale, newFilelist); } message.getCode(); } } /** * @see org.openmrs.messagesource.MutableMessageSource#getPresentation(java.lang.String, * java.util.Locale) */ public PresentationMessage getPresentation(String key, Locale forLocale) { // TODO Auto-generated method stub return null; } /** * @see org.openmrs.messagesource.MutableMessageSource#getPresentationsInLocale(java.util.Locale) */ public Collection<PresentationMessage> getPresentationsInLocale(Locale locale) { // TODO Auto-generated method stub return null; } }