Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
eschleb committed Oct 23, 2024
2 parents 5db35af + 2c428dc commit e74ac67
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 256 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package com.namics.oss.magnolia.dictionary;

import com.namics.oss.magnolia.dictionary.i18nsystem.DictionaryMessageBundlesInstaller;
import com.namics.oss.magnolia.dictionary.i18nsystem.DictionaryTranslationServiceImpl;
import info.magnolia.cms.util.FilteredEventListener;
import info.magnolia.i18nsystem.TranslationService;
import info.magnolia.module.ModuleLifecycle;
import info.magnolia.module.ModuleLifecycleContext;
import info.magnolia.objectfactory.ComponentProvider;
import info.magnolia.objectfactory.Components;
import info.magnolia.observation.WorkspaceEventListenerRegistration;
import info.magnolia.resourceloader.ResourceOrigin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;

import javax.inject.Inject;
import javax.jcr.RepositoryException;
import javax.jcr.observation.Event;
import java.lang.invoke.MethodHandles;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.namics.oss.magnolia.dictionary.i18nsystem.DictionaryMessageBundlesInstaller;
import com.namics.oss.magnolia.dictionary.i18nsystem.DictionaryMessageBundlesLoader;

public class DictionaryModule implements ModuleLifecycle {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Expand All @@ -26,20 +25,24 @@ public class DictionaryModule implements ModuleLifecycle {
private static final String OBSERVATION_PATH = "/";

private final DictionaryMessageBundlesInstaller messagesInstaller;
private WorkspaceEventListenerRegistration.Handle handle;
private final DictionaryMessageBundlesLoader dictionaryMessageBundlesLoader;
private WorkspaceEventListenerRegistration.Handle handle;
private boolean loadLabelsOnStartup = true;

@Inject
public DictionaryModule(final ComponentProvider componentProvider, final ResourceOrigin resourceOrigin) {
messagesInstaller = componentProvider.newInstance(DictionaryMessageBundlesInstaller.class, resourceOrigin);
public DictionaryModule(
final DictionaryMessageBundlesInstaller dictionaryMessageBundlesInstaller,
final DictionaryMessageBundlesLoader dictionaryMessageBundlesLoader
) {
this.messagesInstaller = dictionaryMessageBundlesInstaller;
this.dictionaryMessageBundlesLoader = dictionaryMessageBundlesLoader;
}

@Override
public void start(ModuleLifecycleContext moduleLifecycleContext) {
try {
DictionaryTranslationServiceImpl translationService = (DictionaryTranslationServiceImpl) Components.getComponent(TranslationService.class);
this.handle = WorkspaceEventListenerRegistration
.observe(DictionaryConfiguration.REPOSITORY, OBSERVATION_PATH, new FilteredEventListener(translationService, FilteredEventListener.JCR_SYSTEM_EXCLUDING_PREDICATE))
.observe(DictionaryConfiguration.REPOSITORY, OBSERVATION_PATH, new FilteredEventListener(dictionaryMessageBundlesLoader, FilteredEventListener.JCR_SYSTEM_EXCLUDING_PREDICATE))
.withSubNodes(true)
.withDelay(EVENT_DELAY, EVENT_DELAY_MAX)
.withEventTypesMask(Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_CHANGED)
Expand All @@ -52,6 +55,7 @@ public void start(ModuleLifecycleContext moduleLifecycleContext) {
LOG.info("Start Dictionary module: Load labels to dictionary");
messagesInstaller.loadLabelsToDictionary();
}
dictionaryMessageBundlesLoader.reload();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.namics.oss.magnolia.dictionary.i18nsystem;

import com.machinezoo.noexception.Exceptions;
import com.namics.oss.magnolia.dictionary.DictionaryConfiguration;
import com.namics.oss.magnolia.dictionary.util.DictionaryUtils;
import com.namics.oss.magnolia.dictionary.util.Lazy;
import com.namics.oss.magnolia.dictionary.util.NodeUtil;
import com.namics.oss.magnolia.dictionary.util.predicates.NodeNameFilteringPredicate;
import com.namics.oss.magnolia.dictionary.util.predicates.SystemNodeFilteringPredicate;
Expand All @@ -11,6 +11,7 @@
import info.magnolia.resourceloader.ResourceOrigin;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.commons.predicate.Predicate;
import org.apache.jackrabbit.commons.predicate.Predicates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -24,88 +25,74 @@
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Stream;

public class DictionaryMessageBundlesInstaller {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public static final String LAST_LOADED_TIME = "lastLoadedTime";

private final Lazy<Properties> messages;
private final I18nResourcesProvider i18nResourcesProvider;
private final ResourceOrigin<?> resourceOrigin;

@Inject
@Inject
public DictionaryMessageBundlesInstaller(
final I18nResourcesProvider i18nResourcesProvider,
final ResourceOrigin resourceOrigin) {
this.messages = Lazy.of(() ->
loadProperties(i18nResourcesProvider.getI18nResources(resourceOrigin))
);
}

private Properties loadProperties(final Collection<Resource> resources) {
final Properties properties = new Properties();
resources.stream().map(this::loadProperties).forEach(properties::putAll);
return properties;
}

private Properties loadProperties(final Resource resource) {
final Properties properties = new Properties();
try (InputStream in = resource.openStream()) {
LOG.debug("Loading properties from '{}'", resource);
final Reader inStream = new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8);
properties.load(inStream);
} catch (Exception e) {
LOG.error("Failed to read properties from '{}', skipping properties file...", resource, e);
}
return properties;
}

public Properties getMessages() {
return messages.get();
final ResourceOrigin resourceOrigin
) {
this.i18nResourcesProvider = i18nResourcesProvider;
this.resourceOrigin = resourceOrigin;
}

public void loadLabelsToDictionary() {
Node dictionaryRoot = NodeUtil.getNodeByPathOrNull(DictionaryConfiguration.REPOSITORY, "/");
final Node dictionaryRoot = NodeUtil.getNodeByPathOrNull(DictionaryConfiguration.REPOSITORY, "/");
setLastLoadedTime(dictionaryRoot);

List<String> notExpired = new ArrayList<>();
final Set<String> notExpired = new HashSet<>();

for (Map.Entry<Object, Object> message : getMessages().entrySet()) {
String messageValue = message.getValue().toString();
String messageName = message.getKey().toString();
String messageNodeName = DictionaryUtils.getValidMessageNodeName(messageName);
streamPropertyEntries().forEach(message -> {
final String messageValue = message.getValue().toString();
final String messageName = message.getKey().toString();
final String messageNodeName = DictionaryUtils.getValidMessageNodeName(messageName);
if (!messageName.equals(messageNodeName)) {
LOG.info("Label name changed from '{}' to '{}'", messageName, messageNodeName);
}
// collect list of messages/labels present in the property files
notExpired.add(messageNodeName);
createProperty(dictionaryRoot, messageNodeName, messageValue, messageName);
});

Node labelNode = NodeUtil.getOrCreateNode(dictionaryRoot, messageNodeName, DictionaryConfiguration.NODE_TYPE);
try {
if (propertyNeedsUpdate(labelNode, messageValue)) {
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.NAME, messageName);
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.VALUE, messageValue);
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.EXPIRED, null);
labelNode.getSession().save();
}
} catch (RepositoryException e) {
LOG.warn("Could not create label: '{}'", messageName, e);
}
}
markExpiredProperties(dictionaryRoot, notExpired);
Exceptions.wrap().run(() -> dictionaryRoot.getSession().save());
}

private void markExpiredProperties(final Node dictionaryRoot, final Set<String> notExpired) {
try {
var expiredNodesPredicate = Predicates.and(new NodeNameFilteringPredicate(notExpired), new SystemNodeFilteringPredicate());
Iterable<Node> expiredNodes = NodeUtil.getNodes(dictionaryRoot, expiredNodesPredicate);
final Predicate expiredNodesPredicate = Predicates.and(new NodeNameFilteringPredicate(notExpired), new SystemNodeFilteringPredicate());
final Iterable<Node> expiredNodes = NodeUtil.getNodes(dictionaryRoot, expiredNodesPredicate);
for (Node expiredNode : expiredNodes) {
PropertyUtil.setProperty(expiredNode, DictionaryConfiguration.Prop.EXPIRED, true);
expiredNode.getSession().save();
}
} catch (RepositoryException e) {
LOG.warn("Could not mark expired nodes", e);
}
}

private void createProperty(final Node dictionaryRoot, final String messageNodeName, final String messageValue, final String messageName) {
final Node labelNode = NodeUtil.getOrCreateNode(dictionaryRoot, messageNodeName, DictionaryConfiguration.NODE_TYPE);
try {
if (propertyNeedsUpdate(labelNode, messageValue)) {
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.NAME, messageName);
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.VALUE, messageValue);
PropertyUtil.setProperty(labelNode, DictionaryConfiguration.Prop.EXPIRED, null);
}
} catch (RepositoryException e) {
LOG.warn("Could not create label: '{}'", messageName, e);
}
}

private boolean propertyNeedsUpdate(Node labelNode, String newValue) {
String currentValue = PropertyUtil.getString(labelNode, DictionaryConfiguration.Prop.VALUE);
boolean wasExpired = PropertyUtil.getBoolean(labelNode, DictionaryConfiguration.Prop.EXPIRED, false);
final String currentValue = PropertyUtil.getString(labelNode, DictionaryConfiguration.Prop.VALUE);
final boolean wasExpired = PropertyUtil.getBoolean(labelNode, DictionaryConfiguration.Prop.EXPIRED, false);
return wasExpired || !StringUtils.equals(currentValue, newValue);
}

Expand All @@ -117,4 +104,25 @@ private void setLastLoadedTime(Node dictionaryRoot) {
LOG.error("Could not set time when labels were last loaded. Manually check labels which should get removed.", e);
}
}

private Stream<Map.Entry<Object, Object>> streamPropertyEntries() {
final Collection<Resource> resources = i18nResourcesProvider.getI18nResources(resourceOrigin);
return resources.stream()
.map(this::loadProperties)
.flatMap(properties ->
properties.entrySet().stream()
);
}

private Properties loadProperties(final Resource resource) {
final Properties properties = new Properties();
try (InputStream in = resource.openStream()) {
LOG.debug("Loading properties from '{}'", resource);
final Reader inStream = new InputStreamReader(BOMInputStream.builder().setInputStream(in).get(), StandardCharsets.UTF_8);
properties.load(inStream);
} catch (Exception e) {
LOG.error("Failed to read properties from '{}', skipping properties file...", resource, e);
}
return properties;
}
}
Original file line number Diff line number Diff line change
@@ -1,68 +1,96 @@
package com.namics.oss.magnolia.dictionary.i18nsystem;

import com.namics.oss.magnolia.dictionary.DictionaryConfiguration;
import com.namics.oss.magnolia.dictionary.util.LocaleUtils;
import com.namics.oss.magnolia.dictionary.util.NodeUtil;
import info.magnolia.context.MgnlContext;
import info.magnolia.context.SystemContext;
import info.magnolia.jcr.util.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.util.HashMap;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.inject.Provider;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.machinezoo.noexception.Exceptions;
import com.namics.oss.magnolia.dictionary.DictionaryConfiguration;
import com.namics.oss.magnolia.dictionary.util.LocaleUtils;
import com.namics.oss.magnolia.dictionary.util.NodeUtil;

@Singleton
public class DictionaryMessageBundlesLoader implements EventListener {
private static final Logger LOG = LoggerFactory.getLogger(DictionaryMessageBundlesLoader.class);
private final Provider<SystemContext> systemContextProvider;
private Map<Locale, Properties> messages = Collections.emptyMap();

/**
* @author mrauch, Namics AG
* @since 11.03.2016
*/
public class DictionaryMessageBundlesLoader {
@Inject
public DictionaryMessageBundlesLoader(final Provider<SystemContext> systemContextProvider) {
this.systemContextProvider = systemContextProvider;
}

private static final Logger LOG = LoggerFactory.getLogger(DictionaryMessageBundlesLoader.class);
public void reload() {
Exceptions.wrap().run(() -> {
final Session session = systemContextProvider.get().getJCRSession(DictionaryConfiguration.REPOSITORY);
loadMessages(session);
session.logout();
});
}

private final Map<Locale, Properties> messages = new HashMap<Locale, Properties>();
private void loadMessages(final Session session) throws RepositoryException {
final Node root = session.getRootNode();
messages = LocaleUtils.getLocalesOfAllSiteDefinitions().stream().collect(Collectors.toMap(
Function.identity(),
locale -> getProperties(locale, root))
);
}

public DictionaryMessageBundlesLoader() {
loadMessagesInSystemContext();
}
private Properties getProperties(final Locale locale, final Node root) {
LOG.debug("Loading dictionary properties with locale [{}]...", locale);
final Properties properties = new Properties();
streamMessages(locale, root).forEach(entry ->
properties.put(entry.getKey(), entry.getValue())
);
return properties;
}

protected void loadMessagesInSystemContext() {
try {
MgnlContext.doInSystemContext(new MgnlContext.RepositoryOp() {
@Override
public void doExec() throws RepositoryException {
loadMessages();
}
});
} catch (RepositoryException e) {
LOG.warn("An error occurred while trying to to load dictionary properties", e);
}
}
private Stream<Map.Entry<String, String>> streamMessages(final Locale locale, final Node root) {
return StreamSupport
.stream(
Spliterators.spliteratorUnknownSize(Exceptions.wrap().get(() -> NodeUtil.getNodes(root)).iterator(), Spliterator.ORDERED),
false
)
.map(message ->
Optional.ofNullable(PropertyUtil.getString(message, LocaleUtils.getLocaleString(locale))).map(value ->
Map.entry(NodeUtil.getName(message), value)
)
)
.flatMap(Optional::stream);
}

protected void loadMessages() {
Node root = NodeUtil.getNodeByPathOrNull(DictionaryConfiguration.REPOSITORY, "/");
for (Locale locale : LocaleUtils.getLocalesOfAllSiteDefinitions()) {
try {
Properties properties = new Properties();
for (Node message : NodeUtil.asList(NodeUtil.getNodes(root))) {
String key = message.getName();
String value = PropertyUtil.getString(message, LocaleUtils.getLocaleString(locale));
if (key != null && value != null) {
properties.put(key, value);
}
}
messages.put(locale, properties);
} catch (RepositoryException e) {
LOG.warn("An error occurred while trying to to load dictionary properties with locale[{}]", locale, e);
}
LOG.debug("Loading dictionary properties with locale [{}]...", locale);
}
}
public Map<Locale, Properties> getMessages() {
return messages;
}

public Map<Locale, Properties> getMessages() {
return messages;
}
@Override
public void onEvent(EventIterator events) {
if (events.getSize() > 0) {
reload();
}
}
}
Loading

0 comments on commit e74ac67

Please sign in to comment.