ACS-4180 Add localized properties to the repo event (#1714)

This commit is contained in:
Piotr Żurek
2023-02-02 12:22:08 +01:00
committed by GitHub
parent 556e1923d1
commit 2cdbe6f306
9 changed files with 332 additions and 27 deletions

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Data model classes * Alfresco Data model classes
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -51,6 +51,18 @@ public class MLText extends HashMap<Locale, String>
{ {
private static final long serialVersionUID = -3696135175650511841L; private static final long serialVersionUID = -3696135175650511841L;
/**
* Returns default locale used by the {@link MLText} implementation
*
* @see I18NUtil#getLocale()
*
* @return default locale
*/
public static Locale getDefaultLocale()
{
return I18NUtil.getLocale();
}
public MLText() public MLText()
{ {
super(3, 0.75F); super(3, 0.75F);
@@ -61,13 +73,13 @@ public class MLText extends HashMap<Locale, String>
* *
* @param value the value for the current default locale * @param value the value for the current default locale
* *
* @see I18NUtil#getLocale() * @see #getDefaultLocale()
* @see #MLText(Locale, String) * @see #MLText(Locale, String)
* @see #getDefaultValue() * @see #getDefaultValue()
*/ */
public MLText(String value) public MLText(String value)
{ {
this(I18NUtil.getLocale(), value); this(getDefaultLocale(), value);
} }
/** /**
@@ -124,7 +136,7 @@ public class MLText extends HashMap<Locale, String>
/** /**
* Retrieves a default value from the set of available locales.<br/> * Retrieves a default value from the set of available locales.<br/>
* *
* @see I18NUtil#getLocale() * @see #getDefaultLocale()
* @see #getClosestValue(Locale) * @see #getClosestValue(Locale)
*/ */
public String getDefaultValue() public String getDefaultValue()
@@ -135,7 +147,7 @@ public class MLText extends HashMap<Locale, String>
return null; return null;
} }
// There is some hope of getting a match // There is some hope of getting a match
Locale locale = I18NUtil.getLocale(); Locale locale = getDefaultLocale();
return getClosestValue(locale); return getClosestValue(locale);
} }
@@ -168,7 +180,7 @@ public class MLText extends HashMap<Locale, String>
if (match == null) if (match == null)
{ {
// No close matches for the locale - go for the default locale // No close matches for the locale - go for the default locale
locale = I18NUtil.getLocale(); locale = getDefaultLocale();
match = I18NUtil.getNearestLocale(locale, options); match = I18NUtil.getNearestLocale(locale, options);
if (match == null) if (match == null)
{ {

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -304,6 +304,14 @@ public class EventConsolidator implements EventSupportedPolicies
builder.setProperties(mappedProps); builder.setProperties(mappedProps);
resourceBeforeAllFieldsNull = false; resourceBeforeAllFieldsNull = false;
} }
Map<String, Map<String, String>> localizedProps =helper.getLocalizedPropertiesBefore(changedPropsBefore, after);
if (!localizedProps.isEmpty())
{
builder.setLocalizedProperties(localizedProps);
resourceBeforeAllFieldsNull = false;
}
String name = (String) changedPropsBefore.get(ContentModel.PROP_NAME); String name = (String) changedPropsBefore.get(ContentModel.PROP_NAME);
if (name != null) if (name != null)
{ {

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,6 +25,8 @@
*/ */
package org.alfresco.repo.event2; package org.alfresco.repo.event2;
import static java.util.Optional.ofNullable;
import java.io.Serializable; import java.io.Serializable;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -34,8 +36,11 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import com.google.common.collect.Sets;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.event.v1.model.ContentInfo; import org.alfresco.repo.event.v1.model.ContentInfo;
import org.alfresco.repo.event.v1.model.NodeResource; import org.alfresco.repo.event.v1.model.NodeResource;
@@ -43,6 +48,7 @@ import org.alfresco.repo.event.v1.model.UserInfo;
import org.alfresco.repo.event2.filter.EventFilterRegistry; import org.alfresco.repo.event2.filter.EventFilterRegistry;
import org.alfresco.repo.event2.filter.NodeAspectFilter; import org.alfresco.repo.event2.filter.NodeAspectFilter;
import org.alfresco.repo.event2.filter.NodePropertyFilter; import org.alfresco.repo.event2.filter.NodePropertyFilter;
import org.alfresco.repo.node.MLPropertyInterceptor;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
@@ -152,6 +158,7 @@ public class NodeResourceHelper implements InitializingBean
.setPrimaryAssocQName(getPrimaryAssocQName(nodeRef)) .setPrimaryAssocQName(getPrimaryAssocQName(nodeRef))
.setPrimaryHierarchy(PathUtil.getNodeIdsInReverse(path, false)) .setPrimaryHierarchy(PathUtil.getNodeIdsInReverse(path, false))
.setProperties(mapToNodeProperties(properties)) .setProperties(mapToNodeProperties(properties))
.setLocalizedProperties(mapToNodeLocalizedProperties(properties))
.setAspectNames(getMappedAspects(nodeRef)); .setAspectNames(getMappedAspects(nodeRef));
} }
@@ -200,9 +207,8 @@ public class NodeResourceHelper implements InitializingBean
props.forEach((k, v) -> { props.forEach((k, v) -> {
if (!nodePropertyFilter.isExcluded(k)) if (!nodePropertyFilter.isExcluded(k))
{ {
if (v != null && v instanceof MLText) if (v instanceof MLText)
{ {
//TODO - should we send all of the values if multiple locales exist?
v = ((MLText) v).getDefaultValue(); v = ((MLText) v).getDefaultValue();
} }
@@ -213,6 +219,23 @@ public class NodeResourceHelper implements InitializingBean
return filteredProps; return filteredProps;
} }
public Map<String, Map<String, String>> mapToNodeLocalizedProperties(Map<QName, Serializable> props)
{
Map<String, Map<String, String>> filteredProps = new HashMap<>(props.size());
props.forEach((k, v) -> {
if (!nodePropertyFilter.isExcluded(k) && v instanceof MLText)
{
final MLText mlTextValue = (MLText) v;
final HashMap<String, String> localizedValues = new HashMap<>(mlTextValue.size());
mlTextValue.forEach((locale, text) -> localizedValues.put(locale.toString(), text));
filteredProps.put(getQNamePrefixString(k), localizedValues);
}
});
return filteredProps.isEmpty() ? null : filteredProps;
}
public ContentInfo getContentInfo(Map<QName, Serializable> props) public ContentInfo getContentInfo(Map<QName, Serializable> props)
{ {
final Serializable content = props.get(ContentModel.PROP_CONTENT); final Serializable content = props.get(ContentModel.PROP_CONTENT);
@@ -313,11 +336,6 @@ public class NodeResourceHelper implements InitializingBean
return filteredAspects; return filteredAspects;
} }
private boolean isNotEmptyString(Serializable ser)
{
return !(ser instanceof String) || !((String) ser).isEmpty();
}
public QName getNodeType(NodeRef nodeRef) public QName getNodeType(NodeRef nodeRef)
{ {
return nodeService.getType(nodeRef); return nodeService.getType(nodeRef);
@@ -329,8 +347,59 @@ public class NodeResourceHelper implements InitializingBean
} }
public Map<QName, Serializable> getProperties(NodeRef nodeRef) public Map<QName, Serializable> getProperties(NodeRef nodeRef)
{
//We need to have full MLText properties here. This is why we are marking the current thread as MLAware
final boolean toRestore = MLPropertyInterceptor.isMLAware();
MLPropertyInterceptor.setMLAware(true);
try
{ {
return nodeService.getProperties(nodeRef); return nodeService.getProperties(nodeRef);
} finally
{
MLPropertyInterceptor.setMLAware(toRestore);
}
}
public Map<String, Map<String, String>> getLocalizedPropertiesBefore(Map<QName, Serializable> propsBefore, NodeResource nodeAfter)
{
final Map<String, Map<String, String>> locPropsBefore = ofNullable(propsBefore)
.map(this::mapToNodeLocalizedProperties)
.orElseGet(Map::of);
final Map<String, Map<String, String>> locPropsAfter = ofNullable(nodeAfter)
.map(NodeResource::getLocalizedProperties)
.orElseGet(Map::of);
return getLocalizedPropertiesBefore(locPropsBefore, locPropsAfter);
}
static Map<String, Map<String, String>> getLocalizedPropertiesBefore(Map<String, Map<String, String>> locPropsBefore,
Map<String, Map<String, String>> locPropsAfter)
{
final Map<String, Map<String, String>> result = new HashMap<>(locPropsBefore.size());
Sets.union(locPropsBefore.keySet(), locPropsAfter.keySet()).forEach(propertyName -> {
final Map<String, String> valuesBefore = ofNullable(locPropsBefore.get(propertyName)).orElseGet(Map::of);
final Map<String, String> valuesAfter = ofNullable(locPropsAfter.get(propertyName)).orElseGet(Map::of);
if (!valuesAfter.isEmpty() || !valuesBefore.isEmpty())
{
final Map<String, String> diff = new HashMap<>(valuesBefore.size());
Sets.union(valuesBefore.keySet(), valuesAfter.keySet()).forEach(lang -> {
final String valueBefore = valuesBefore.get(lang);
final String valueAfter = valuesAfter.get(lang);
if (!Objects.equals(valueBefore, valueAfter))
{
diff.put(lang, valueBefore);
}
});
if (!diff.isEmpty())
{
result.put(propertyName, diff);
}
}
});
return result;
} }
public Set<String> getMappedAspects(NodeRef nodeRef) public Set<String> getMappedAspects(NodeRef nodeRef)

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -32,6 +32,7 @@ import static org.awaitility.Awaitility.await;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.jms.ConnectionFactory; import javax.jms.ConnectionFactory;
@@ -47,6 +48,7 @@ import org.alfresco.repo.event.v1.model.Resource;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.dictionary.CustomModelService; import org.alfresco.service.cmr.dictionary.CustomModelService;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
@@ -89,6 +91,11 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
private static final String CAMEL_ROUTE = "jms:topic:" + TOPIC_NAME; private static final String CAMEL_ROUTE = "jms:topic:" + TOPIC_NAME;
private static final CamelContext CAMEL_CONTEXT = new DefaultCamelContext(); private static final CamelContext CAMEL_CONTEXT = new DefaultCamelContext();
protected final Locale defaultLocale = new Locale(MLText.getDefaultLocale().getLanguage());
protected final Locale germanLocale = new Locale(Locale.GERMAN.getLanguage(), "XX");
protected final Locale frenchLocale = new Locale(Locale.FRENCH.getLanguage());
protected final Locale japaneseLocale = new Locale(Locale.JAPANESE.getLanguage(), "YY", "ZZ");
private static boolean isCamelConfigured; private static boolean isCamelConfigured;
private static DataFormat dataFormat; private static DataFormat dataFormat;
@@ -388,6 +395,20 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
return (T) resource.getProperties().get(propertyName); return (T) resource.getProperties().get(propertyName);
} }
protected String getLocalizedProperty(NodeResource resource, String propertyName, Locale locale)
{
assertTrue(containsLocalizedProperty(resource, propertyName, locale));
return resource.getLocalizedProperties().get(propertyName).get(locale.toString());
}
protected boolean containsLocalizedProperty(NodeResource resource, String propertyName, Locale locale)
{
assertNotNull(resource);
assertNotNull(resource.getLocalizedProperties());
assertNotNull(resource.getLocalizedProperties().get(propertyName));
return resource.getLocalizedProperties().get(propertyName).containsKey(locale.toString());
}
/** /**
* Await at most 5 seconds for the condition (ie. {@code numOfEvents}) * Await at most 5 seconds for the condition (ie. {@code numOfEvents})
* to be met before throwing a timeout exception. * to be met before throwing a timeout exception.

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -38,6 +38,7 @@ import org.alfresco.repo.event.v1.model.EventData;
import org.alfresco.repo.event.v1.model.EventType; import org.alfresco.repo.event.v1.model.EventType;
import org.alfresco.repo.event.v1.model.NodeResource; import org.alfresco.repo.event.v1.model.NodeResource;
import org.alfresco.repo.event.v1.model.RepoEvent; import org.alfresco.repo.event.v1.model.RepoEvent;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
@@ -50,7 +51,6 @@ import org.springframework.beans.factory.annotation.Autowired;
*/ */
public class CreateRepoEventIT extends AbstractContextAwareRepoEvent public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
{ {
@Autowired @Autowired
private NodeDAO nodeDAO; private NodeDAO nodeDAO;
@@ -63,6 +63,9 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
PropertyMap propertyMap = new PropertyMap(); PropertyMap propertyMap = new PropertyMap();
propertyMap.put(ContentModel.PROP_TITLE, "test title"); propertyMap.put(ContentModel.PROP_TITLE, "test title");
propertyMap.put(ContentModel.PROP_NAME, name); propertyMap.put(ContentModel.PROP_NAME, name);
final MLText localizedDescription = new MLText(germanLocale, "german description");
localizedDescription.addValue(defaultLocale, "default description");
propertyMap.put(ContentModel.PROP_DESCRIPTION, localizedDescription);
final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap); final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap);
final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(1); final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(1);
@@ -91,6 +94,10 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
assertNotNull(nodeResource.getPrimaryHierarchy()); assertNotNull(nodeResource.getPrimaryHierarchy());
assertNotNull("Default aspects were not added. ", nodeResource.getAspectNames()); assertNotNull("Default aspects were not added. ", nodeResource.getAspectNames());
assertEquals("test title", getProperty(nodeResource, "cm:title")); assertEquals("test title", getProperty(nodeResource, "cm:title"));
assertEquals("test title", getLocalizedProperty(nodeResource, "cm:title", defaultLocale));
assertEquals("default description", getProperty(nodeResource, "cm:description"));
assertEquals("default description", getLocalizedProperty(nodeResource, "cm:description", defaultLocale));
assertEquals("german description", getLocalizedProperty(nodeResource, "cm:description", germanLocale));
assertNull("There is no content.", nodeResource.getContent()); assertNull("There is no content.", nodeResource.getContent());
assertNotNull("Missing createdByUser property.", nodeResource.getCreatedByUser()); assertNotNull("Missing createdByUser property.", nodeResource.getCreatedByUser());

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -31,6 +31,7 @@ import org.alfresco.repo.event.v1.model.EventData;
import org.alfresco.repo.event.v1.model.EventType; import org.alfresco.repo.event.v1.model.EventType;
import org.alfresco.repo.event.v1.model.NodeResource; import org.alfresco.repo.event.v1.model.NodeResource;
import org.alfresco.repo.event.v1.model.RepoEvent; import org.alfresco.repo.event.v1.model.RepoEvent;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
@@ -48,6 +49,9 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent
String localName = GUID.generate(); String localName = GUID.generate();
PropertyMap propertyMap = new PropertyMap(); PropertyMap propertyMap = new PropertyMap();
propertyMap.put(ContentModel.PROP_TITLE, "test title"); propertyMap.put(ContentModel.PROP_TITLE, "test title");
final MLText localizedDescription = new MLText(germanLocale, "german description");
localizedDescription.addValue(defaultLocale, "default description");
propertyMap.put(ContentModel.PROP_DESCRIPTION, localizedDescription);
NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap); NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap);
NodeResource createdResource = getNodeResource(1); NodeResource createdResource = getNodeResource(1);
@@ -62,10 +66,16 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent
deleteNode(nodeRef); deleteNode(nodeRef);
final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(2); final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(2);
final NodeResource nodeResource = getNodeResource(resultRepoEvent);
assertEquals("Repo event type:", EventType.NODE_DELETED.getType(), resultRepoEvent.getType()); assertEquals("Repo event type:", EventType.NODE_DELETED.getType(), resultRepoEvent.getType());
assertEquals(createdResource.getId(), getNodeResource(resultRepoEvent).getId()); assertEquals(createdResource.getId(), nodeResource.getId());
assertEquals("Wrong primaryAssocQName prefix.", "ce:" + localName, createdResource.getPrimaryAssocQName()); assertEquals("Wrong primaryAssocQName prefix.", "ce:" + localName, createdResource.getPrimaryAssocQName());
assertEquals("test title", getProperty(nodeResource, "cm:title"));
assertEquals("test title", getLocalizedProperty(nodeResource, "cm:title", defaultLocale));
assertEquals("default description", getProperty(nodeResource, "cm:description"));
assertEquals("default description", getLocalizedProperty(nodeResource, "cm:description", defaultLocale));
assertEquals("german description", getLocalizedProperty(nodeResource, "cm:description", germanLocale));
// There should be no resourceBefore // There should be no resourceBefore
EventData<NodeResource> eventData = getEventData(resultRepoEvent); EventData<NodeResource> eventData = getEventData(resultRepoEvent);

View File

@@ -0,0 +1,114 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.event2;
import static org.alfresco.repo.event2.NodeResourceHelper.getLocalizedPropertiesBefore;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class NodeResourceHelperUnitTest
{
@Test
public void shouldExtractOnlyRelevantPropertiesForBeforeNode()
{
final Map<String, Map<String, String>> before =
Map.of(
"unchanged-empty", locValues(),
"unchanged-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-added", locValues("pl", "Kiełbasa"),
"changed-modified", locValues("pl", "XYZ", "en", "Sausage"),
"changed-deleted", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-added-modified-deleted", locValues("pl", "XYZ", "en", "Sausage"),
"changed-to-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-from-empty", locValues(),
"removed-empty", locValues(),
"removed-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage")
);
final Map<String, Map<String, String>> after =
Map.of(
"unchanged-empty", locValues(),
"unchanged-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-added", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-modified", locValues("pl", "Kiełbasa", "en", "Sausage"),
"changed-deleted", locValues("en", "Sausage"),
"changed-added-modified-deleted", locValues("pl", "Kiełbasa", "de", "Würst"),
"changed-to-empty", locValues(),
"changed-from-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
"new-empty", locValues(),
"new-non-empty", locValues("de", "Würst")
);
final Map<String, Map<String, String>> diff = getLocalizedPropertiesBefore(before, after);
assertFalse(diff.containsKey("unchanged-empty"));
assertFalse(diff.containsKey("unchanged-non-empty"));
assertEquals(locValues("en", null), diff.get("changed-added"));
assertEquals(locValues("pl", "XYZ"), diff.get("changed-modified"));
assertEquals(locValues("pl", "Kiełbasa"), diff.get("changed-deleted"));
assertEquals(locValues("pl", "XYZ", "en", "Sausage", "de", null), diff.get("changed-added-modified-deleted"));
assertEquals(locValues("pl", "Kiełbasa", "en", "Sausage"), diff.get("changed-to-empty"));
assertEquals(locValues("pl", null, "en", null), diff.get("changed-from-empty"));
assertFalse(diff.containsKey("removed-empty"));
assertEquals(locValues("pl", "Kiełbasa", "en", "Sausage"), diff.get("removed-non-empty"));
assertFalse(diff.containsKey("new-empty"));
assertEquals(locValues("de", null), diff.get("new-non-empty"));
}
private LocalizedValues locValues(String l1, String v1, String l2, String v2, String l3, String v3)
{
return locValues(l1, v1, l2, v2).append(l3, v3);
}
private LocalizedValues locValues(String l1, String v1, String l2, String v2)
{
return locValues(l1, v1).append(l2, v2);
}
private LocalizedValues locValues(String l1, String v1)
{
return locValues().append(l1, v1);
}
private LocalizedValues locValues()
{
return new LocalizedValues();
}
private static class LocalizedValues extends HashMap<String, String>
{
public LocalizedValues append(String language, String value)
{
this.put(language, value);
return this;
}
}
}

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -34,7 +34,8 @@ import org.junit.runners.Suite.SuiteClasses;
@SuiteClasses({ EventFilterUnitTest.class, @SuiteClasses({ EventFilterUnitTest.class,
EventConsolidatorUnitTest.class, EventConsolidatorUnitTest.class,
EventJSONSchemaUnitTest.class, EventJSONSchemaUnitTest.class,
EventGeneratorQueueUnitTest.class EventGeneratorQueueUnitTest.class,
NodeResourceHelperUnitTest.class
}) })
public class RepoEvent2UnitSuite public class RepoEvent2UnitSuite
{ {

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,6 +26,8 @@
package org.alfresco.repo.event2; package org.alfresco.repo.event2;
import static org.alfresco.model.ContentModel.PROP_DESCRIPTION;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -46,6 +48,7 @@ import org.alfresco.service.cmr.dictionary.CustomModelDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
@@ -279,6 +282,60 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
assertNull(resourceBefore.getPrimaryAssocQName()); assertNull(resourceBefore.getPrimaryAssocQName());
} }
@Test
public void testUpdateContentWithLocalizedProperties()
{
final String description = "cm:description";
final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT);
NodeResource resource = getNodeResource(1);
assertNull(getProperty(resource, description));
assertNull(resource.getLocalizedProperties());
assertNull(getEventData(1).getResourceBefore());
retryingTransactionHelper.doInTransaction(() -> {
final MLText localizedDescription = new MLText(germanLocale, "german description");
localizedDescription.addValue(defaultLocale, "default description");
localizedDescription.addValue(japaneseLocale, "japanese description");
nodeService.setProperty(nodeRef, PROP_DESCRIPTION, localizedDescription);
return null;
});
resource = getNodeResource(2);
NodeResource resourceBefore = getNodeResourceBefore(2);
assertEquals("default description", getProperty(resource, description));
assertEquals("default description", getLocalizedProperty(resource, description, defaultLocale));
assertEquals("german description", getLocalizedProperty(resource, description, germanLocale));
assertEquals("japanese description", getLocalizedProperty(resource, description, japaneseLocale));
assertNull(getLocalizedProperty(resourceBefore, description, defaultLocale));
assertNull(getLocalizedProperty(resourceBefore, description, germanLocale));
assertNull(getLocalizedProperty(resourceBefore, description, japaneseLocale));
retryingTransactionHelper.doInTransaction(() -> {
final MLText localizedDescription = new MLText(frenchLocale, "french description added");
localizedDescription.addValue(defaultLocale, "default description modified");
localizedDescription.addValue(japaneseLocale, "japanese description");
nodeService.setProperty(nodeRef, PROP_DESCRIPTION, localizedDescription);
return null;
});
resource = getNodeResource(3);
resourceBefore = getNodeResourceBefore(3);
assertEquals("default description modified", getProperty(resource, description));
assertEquals("default description modified", getLocalizedProperty(resource, description, defaultLocale));
assertEquals("french description added", getLocalizedProperty(resource, description, frenchLocale));
assertEquals("japanese description", getLocalizedProperty(resource, description, japaneseLocale));
assertFalse(containsLocalizedProperty(resource, description, germanLocale));
assertEquals("default description", getLocalizedProperty(resourceBefore, description, defaultLocale));
assertEquals("german description", getLocalizedProperty(resourceBefore, description, germanLocale));
assertNull(getLocalizedProperty(resourceBefore, description, frenchLocale));
assertFalse(containsLocalizedProperty(resourceBefore, description, japaneseLocale));
}
@Test @Test
public void testUpdateContentTitle() public void testUpdateContentTitle()
{ {
@@ -296,8 +353,11 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
}); });
resource = getNodeResource(2); resource = getNodeResource(2);
NodeResource resourceBefore = getNodeResourceBefore(2);
title = getProperty(resource, "cm:title"); title = getProperty(resource, "cm:title");
assertEquals("test title", title); assertEquals("test title", title);
assertEquals("test title", getLocalizedProperty(resource, "cm:title", defaultLocale));
assertNull(getLocalizedProperty(resourceBefore, "cm:title", defaultLocale));
// update content cm:title property again with "new test title" value // update content cm:title property again with "new test title" value
retryingTransactionHelper.doInTransaction(() -> { retryingTransactionHelper.doInTransaction(() -> {
@@ -308,10 +368,13 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
resource = getNodeResource(3); resource = getNodeResource(3);
title = getProperty(resource, "cm:title"); title = getProperty(resource, "cm:title");
assertEquals("new test title", title); assertEquals("new test title", title);
assertEquals("new test title", getLocalizedProperty(resource, "cm:title", defaultLocale));
NodeResource resourceBefore = getNodeResourceBefore(3);
resourceBefore = getNodeResourceBefore(3);
title = getProperty(resourceBefore, "cm:title"); title = getProperty(resourceBefore, "cm:title");
assertEquals("Wrong old property.", "test title", title); assertEquals("Wrong old property.", "test title", title);
assertEquals("test title", getLocalizedProperty(resourceBefore, "cm:title", defaultLocale));
assertNotNull(resourceBefore.getModifiedAt()); assertNotNull(resourceBefore.getModifiedAt());
} }
@@ -354,7 +417,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
// update content cm:description property with "test_description" value // update content cm:description property with "test_description" value
retryingTransactionHelper.doInTransaction(() -> { retryingTransactionHelper.doInTransaction(() -> {
nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "test description"); nodeService.setProperty(nodeRef, PROP_DESCRIPTION, "test description");
return null; return null;
}); });
@@ -492,7 +555,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
QName.createQName(TEST_NAMESPACE, GUID.generate()), QName.createQName(TEST_NAMESPACE, GUID.generate()),
ContentModel.TYPE_CONTENT).getChildRef(); ContentModel.TYPE_CONTENT).getChildRef();
nodeService.setProperty(node1, ContentModel.PROP_DESCRIPTION, "test description"); nodeService.setProperty(node1, PROP_DESCRIPTION, "test description");
return null; return null;
}); });
//Create and update node are done in the same transaction so one event is expected //Create and update node are done in the same transaction so one event is expected