/**
* This file Copyright (c) 2003-2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.cms.i18n;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.ItemType;
import info.magnolia.cms.util.NodeDataUtil;
import info.magnolia.cms.util.ObservationUtil;
import info.magnolia.context.MgnlContext;
import info.magnolia.jcr.node2bean.Node2BeanProcessor;
import info.magnolia.jcr.node2bean.TransformationState;
import info.magnolia.jcr.node2bean.TypeDescriptor;
import info.magnolia.jcr.node2bean.TypeMapping;
import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl;
import info.magnolia.objectfactory.ComponentProvider;
import info.magnolia.objectfactory.Components;
import info.magnolia.repository.RepositoryConstants;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default MessagesManager implementation.
* @author philipp
*
* @version $Id$
*/
@Singleton
public class DefaultMessagesManager extends MessagesManager {
private final static Logger log = LoggerFactory.getLogger(DefaultMessagesManager.class);
/**
* The current locale of the application.
*/
private Locale applicationLocale;
/**
* List of the available locales.
*/
private final Collection<Locale> availableLocales = new ArrayList<Locale>();
/**
* Map for the messages.
*/
private Map messages;
private String defaultBasename = DEFAULT_BASENAME;
private final Node2BeanProcessor nodeToBean;
@Deprecated
public DefaultMessagesManager() {
this(Components.getComponent(Node2BeanProcessor.class));
}
@Inject
public DefaultMessagesManager(Node2BeanProcessor nodeToBean) {
this.nodeToBean = nodeToBean;
// setting default language (en)
setDefaultLocale(FALLBACK_LOCALE);
initMap();
}
// for tests
void setDefaultBasename(String defaultBasename) {
this.defaultBasename = defaultBasename;
}
/**
* Called through the initialization process. (startup of the container)
*/
@Override
public void init() {
load();
registerEventListener();
}
/**
* The lazy Map creates messages objects with a fall back to the default locale.
*/
protected void initMap() {
// FIXME use LRU: new LRUMap(20);
// LazyMap will instanciate bundles on demand.
final Map map = LazyMap.decorate(new HashMap(), new Transformer() {
// this transformer will wrap the Messages in a MessagesChain which
// will fall back to a Messages instance for the same bundle with
// default locale.
@Override
public Object transform(Object input) {
final MessagesID id = (MessagesID) input;
return newMessages(id);
}
});
messages = Collections.synchronizedMap(map);
}
/**
* Initializes a new Messages instances for the given MessagesID. By default, we chain to the same bundle with the
* default Locale. (so untranslated messages show up in the default language)
*/
protected Messages newMessages(MessagesID messagesID) {
Messages msgs = new DefaultMessagesImpl(messagesID.basename, messagesID.locale);
if (!getDefaultLocale().equals(messagesID.locale)) {
msgs = new MessagesChain(msgs).chain(getMessages(messagesID.basename, getDefaultLocale()));
}
return msgs;
}
/**
* Load i18n configuration.
*/
protected void load() {
// reading the configuration from the repository, no need for context
HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
try {
log.info("Loading i18n configuration - {}", I18N_CONFIG_PATH);
// checks if node exists
if (!hm.isExist(I18N_CONFIG_PATH)) {
// configNode = ContentUtil.createPath(hm, I18N_CONFIG_PATH, ItemType.CONTENT, true);
log.warn("{} does not exist yet; skipping.", I18N_CONFIG_PATH);
return;
}
final Content configNode = hm.getContent(I18N_CONFIG_PATH);
setDefaultLocale(NodeDataUtil.getString(configNode, FALLBACK_NODEDATA, FALLBACK_LOCALE));
// get the available languages - creates it if it does not exist - necessary to update to 3.5
final Content languagesNode;
if (configNode.hasContent(LANGUAGES_NODE_NAME)) {
languagesNode = configNode.getContent(LANGUAGES_NODE_NAME);
}
else {
languagesNode = configNode.createContent(LANGUAGES_NODE_NAME, ItemType.CONTENT);
}
final Map<String, LocaleDefinition> languageDefinitions = (Map<String, LocaleDefinition>) nodeToBean.setProperties(new LinkedHashMap<String, LocaleDefinition>(), languagesNode.getJCRNode(), true, new Node2BeanTransformerImpl() {
@Override
protected TypeDescriptor onResolveType(TypeMapping typeMapping, TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
if (resolvedType == null && state.getLevel() == 2) {
return typeMapping.getTypeDescriptor(LocaleDefinition.class);
}
return resolvedType;
}
}, Components.getComponentProvider());
// clear collection for reload
availableLocales.clear();
for (LocaleDefinition ld : languageDefinitions.values()) {
if (ld.isEnabled()) {
availableLocales.add(ld.getLocale());
}
}
} catch (Exception e) {
log.error("Failed to load i18n configuration - {}", I18N_CONFIG_PATH, e);
}
}
/**
* Register an event listener: reload configuration when something changes.
*/
private void registerEventListener() {
log.info("Registering event listener for i18n");
ObservationUtil.registerChangeListener(RepositoryConstants.CONFIG, I18N_CONFIG_PATH, new EventListener() {
@Override
public void onEvent(EventIterator iterator) {
// reload everything
reload();
}
});
}
/**
* Reload i18n configuration.
*/
@Override
public void reload() {
try {
// reload all present
for (Iterator iter = messages.values().iterator(); iter.hasNext();) {
Messages msgs = (Messages) iter.next();
msgs.reload();
}
}
catch (Exception e) {
log.error("Can't reload i18n messages", e);
}
initMap();
load();
}
@Override
public Messages getMessagesInternal(String basename, Locale locale) {
if (StringUtils.isEmpty(basename)) {
basename = defaultBasename;
}
return (Messages) messages.get(new MessagesID(basename, locale));
}
@Override
public Locale getDefaultLocale() {
return applicationLocale;
}
/**
* @param defaultLocale The defaultLocale to set.
* @deprecated since 4.0 - not used and should not be. Use setLocale() on the SystemContext instead. --note: do not
* remove the method, make it private. applicationLocale field is still needed. --and/or remove duplication with
* SystemContext.locale
*/
@Deprecated
public void setDefaultLocale(String defaultLocale) {
this.applicationLocale = new Locale(defaultLocale);
// MgnlContext.getSystemContext().setLocale(applicationLocale);
}
@Override
public Collection getAvailableLocales() {
return availableLocales;
}
public void setMessages(Map messages) {
this.messages = messages;
}
/**
* Used as the key in the Map.
* @author Philipp Bracher
* @version $Revision$ ($Author$)
*/
public static class MessagesID {
private final String basename;
private final Locale locale;
public MessagesID(String basename, Locale locale) {
this.basename = basename;
this.locale = locale;
}
// generated equals and hashcode methods
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MessagesID that = (MessagesID) o;
if (basename != null ? !basename.equals(that.basename) : that.basename != null) {
return false;
}
if (locale != null ? !locale.equals(that.locale) : that.locale != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = basename != null ? basename.hashCode() : 0;
result = 31 * result + (locale != null ? locale.hashCode() : 0);
return result;
}
/**
* Returns the basename.
* @return the basename
*/
public String getBasename() {
return basename;
}
/**
* Returns the locale.
* @return the locale
*/
public Locale getLocale() {
return locale;
}
}
}