/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.application.master;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.mifos.accounts.savings.persistence.GenericDao;
import org.mifos.application.admin.servicefacade.CustomizedTextServiceFacade;
import org.mifos.application.admin.servicefacade.PersonnelServiceFacade;
import org.mifos.application.master.business.LookUpEntity;
import org.mifos.application.master.business.LookUpLabelEntity;
import org.mifos.application.master.business.LookUpValueEntity;
import org.mifos.application.master.business.LookUpValueLocaleEntity;
import org.mifos.application.master.business.MasterDataEntity;
import org.mifos.application.master.persistence.LegacyMasterDao;
import org.mifos.application.servicefacade.ApplicationContextProvider;
import org.mifos.config.Localization;
import org.mifos.config.LocalizedTextLookup;
import org.mifos.config.exceptions.ConfigurationException;
import org.mifos.config.persistence.ApplicationConfigurationDao;
import org.mifos.config.util.helpers.LabelKey;
import org.mifos.customers.office.business.OfficeLevelEntity;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.hibernate.helper.StaticHibernateUtil;
import org.mifos.framework.util.helpers.FilePaths;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
/**
* This class looks up messages from tables like {@link LookUpValueEntity}, {@link LookUpValueLocaleEntity} and the
* like.
* <p>
* The idea is that we'll be able to come up with a simpler mechanism than the rather convoluted one in
* {@link LegacyMasterDao}, {@link MasterDataEntity}, etc. Or at least we can centralize where we call the convoluted
* mechanism.
* <p>
* Also see {@link ApplicationConfigurationDao}.
* <p>
* The word "label" might be better than "message"; at least that's what we call them in places like
* LabelConfigurationAction.
* <p>
* An initial pass has been made at moving to resource bundle based localization. A Spring MessageSource is injected
* into the single instance of MessageLookup. The MessageSource is used to manage the loading and lookup from external
* resource bundle files. See {@link FilePaths#SPRING_CONFIG_CORE} for the Spring configuration.
* <p>
* Enumerated types which implement the {@link LocalizedTextLookup} interface can be passed to {@link MessageLookup} to
* look up a localized text string for each instance of the enumerated type.
* <p>
* Text strings for enumerated types can currently be found in
* org/mifos/config/localizedResources/MessageLookupMessages.properties (and associated versions for different locales).
*/
public class MessageLookup implements MessageSourceAware, FactoryBean<MessageLookup> {
@Autowired
private ApplicationConfigurationDao applicationConfigurationDao;
private Map<LabelKey, String> labelCache = new ConcurrentHashMap<LabelKey, String>();
@Autowired
private GenericDao genericDao;
@Autowired
LegacyMasterDao legacyMasterDao;
@Autowired
CustomizedTextServiceFacade customizedTextServiceFacade;
@Autowired
PersonnelServiceFacade personnelServiceFacade;
private MessageSource messageSource;
public String replaceSubstitutions(String message) {
return customizedTextServiceFacade.replaceSubstitutions(message);
}
public String lookup(LocalizedTextLookup namedObject) {
return replaceSubstitutions(lookup(namedObject.getPropertiesKey()));
}
public String lookup(LocalizedTextLookup namedObject, Object[] params) {
Locale locale = personnelServiceFacade.getUserPreferredLocale();
return replaceSubstitutions(messageSource.getMessage(
namedObject.getPropertiesKey(), params, namedObject.getPropertiesKey(), locale));
}
public String lookup(String lookupKey) {
String msg;
try {
String textMessage = getLabel(lookupKey);
// if we don't find a message above, then it means that it has not
// been customized and
// we should return the default message from the properties file
if (StringUtils.isEmpty(textMessage)) {
Locale locale = personnelServiceFacade.getUserPreferredLocale();
msg = replaceSubstitutions(messageSource.getMessage(lookupKey, null, lookupKey, locale));
}
else {
msg = replaceSubstitutions(textMessage);
}
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
return msg;
}
/*
* Return a label for given label key. Label keys are listed in {@link ConfigurationConstants}.
*/
public String lookupLabel(String labelKey) {
try {
String labelText = getLabel(labelKey);
// if we don't find a label here, then it means that it has not been
// customized and
// we should return the default label from the properties file
return StringUtils.isEmpty(labelText) ? lookup(labelKey + ".Label") : labelText;
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
/*
* Set a custom label value that will override resource bundle values.
*
* TODO: we need to add a method for getting and/or setting a label value directly rather than having to iterate.
* Also, we don't necessarily want to reinitialize the MifosConfiguration after each update. Ultimately, it would be
* cleaner to just use a key-value lookup to implement these overrides.
*/
public void setCustomLabel(String labelKey, String value) throws PersistenceException {
// only update the value if there is a change
if (lookupLabel(labelKey).compareTo(value) != 0) {
for (LookUpEntity entity : applicationConfigurationDao.findLookupEntities()) {
if (entity.getEntityType().equals(labelKey)) {
Set<LookUpLabelEntity> labels = entity.getLookUpLabels();
for (LookUpLabelEntity label : labels) {
label.setLabelName(value);
genericDao.createOrUpdate(label);
StaticHibernateUtil.commitTransaction();
updateLookupValueInCache(labelKey, value);
}
}
}
}
}
public void updateLookupValueInCache(LocalizedTextLookup keyContainer, String newValue) {
updateLookupValueInCache(keyContainer.getPropertiesKey(), newValue);
}
public void updateLookupValueInCache(String lookupKey, String newValue) {
synchronized (labelCache) {
LabelKey key = new LabelKey(lookupKey, getLocaleId());
if (labelCache.containsKey(key)) {
labelCache.remove(key);
labelCache.put(key, newValue);
} else {
labelCache.put(key, newValue);
}
}
}
/**
* @deprecated - don't use from pojo domain model for updating entities lookup values
*
* @see OfficeLevelEntity#update(String)
*/
@Deprecated
public void updateLookupValue(LookUpValueEntity lookupValueEntity, String newValue) {
Set<LookUpValueLocaleEntity> lookUpValueLocales = lookupValueEntity.getLookUpValueLocales();
if ((lookUpValueLocales != null) && StringUtils.isNotBlank(newValue)) {
for (LookUpValueLocaleEntity entity : lookUpValueLocales) {
if (entity.getLookUpId().equals(lookupValueEntity.getLookUpId())
&& (entity.getLookUpValue() == null || !entity.getLookUpValue().equals(newValue))) {
entity.setLookUpValue(newValue);
try {
legacyMasterDao.createOrUpdate(entity);
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
}
updateLookupValueInCache(lookupValueEntity.getLookUpName(), newValue);
break;
}
}
}
}
public void updateLabelKey(String keyString, String newLabelValue, Short localeId) {
synchronized (labelCache) {
LabelKey key = new LabelKey(keyString, localeId);
if (labelCache.containsKey(key)) {
labelCache.remove(key);
labelCache.put(key, newLabelValue);
} else {
labelCache.put(key, newLabelValue);
}
}
}
public void updateLabelCache() {
List<LookUpValueEntity> lookupValueEntities = applicationConfigurationDao.findLookupValues();
for (LookUpValueEntity lookupValueEntity : lookupValueEntities) {
String keyString = lookupValueEntity.getPropertiesKey();
if (keyString == null) {
throw new IllegalStateException("Key is empty");
}
String messageText = lookupValueEntity.getMessageText();
if (StringUtils.isBlank(messageText)) {
messageText = lookup(keyString);
}
labelCache.put(new LabelKey(keyString, getLocaleId()), messageText);
}
}
public void deleteKey(String lookupValueKey) {
synchronized (labelCache) {
LabelKey key = new LabelKey(lookupValueKey, getLocaleId());
if (labelCache.containsKey(key)) {
labelCache.remove(key);
}
}
}
public Map<LabelKey, String> getLabelCache() {
return labelCache;
}
public String getLabel(String key) throws ConfigurationException {
// we only use localeId 1 to store labels since it is an override for
// all locales
return (key == null) ? null : labelCache.get(new LabelKey(key, getLocaleId()));
}
private Short getLocaleId() {
return Localization.getInstance().getLocaleId(personnelServiceFacade.getUserPreferredLocale());
}
public static String getLocalizedMessage(String key) {
// nothing found so return the key
String message = key;
PersonnelServiceFacade personnelServiceFacade = ApplicationContextProvider.getBean(PersonnelServiceFacade.class);
MessageSource messageSource = ApplicationContextProvider.getBean(MessageSource.class);
if(personnelServiceFacade != null) {
String value = messageSource.getMessage(key, null, personnelServiceFacade.getUserPreferredLocale());
if(StringUtils.isNotEmpty(message)) {
message = value;
}
}
return message;
}
/**
* This is a dependency injection method used by Spring to inject a MessageSource for resource bundle based message
* lookup.
*/
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Override
public MessageLookup getObject() throws Exception {
if(labelCache.isEmpty()) {
updateLabelCache();
}
return this;
}
@Override
public Class<MessageLookup> getObjectType() {
return MessageLookup.class;
}
@Override
public boolean isSingleton() {
return true;
}
}