/**
* 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;
}
}