MNT-18700 - Dynamic Message bundles do not deploy/reload automatically (#805)

* Restored the ability to register dynamic messages on bootstrap
* Added unit tests on bootstrap to verify if the dynamic messages are registered on bootstrap
* Added unit tests for the Repo Admin Console regarding registering dynamic messages and classpath files
This commit is contained in:
evasques
2021-11-18 15:48:40 +00:00
committed by GitHub
parent 2c51af33d3
commit be4fa79c76
6 changed files with 282 additions and 27 deletions

View File

@@ -415,12 +415,13 @@ implements TenantDeployer, DictionaryListener, /*TenantDictionaryListener, */Mes
for (NodeRef messageResource : nodeRefs) for (NodeRef messageResource : nodeRefs)
{ {
String resourceName = (String) nodeService.getProperty(messageResource, ContentModel.PROP_NAME); String messageResourcePath = nodeService.getPath(messageResource).toPrefixString(namespaceService);
String bundleBasePrefixedName = messageResourcePath.substring(messageResourcePath.lastIndexOf("/"));
String bundleBaseName = messageService.getBaseBundleName(resourceName); String bundleBaseName = messageService.getBaseBundleName(bundleBasePrefixedName);
if (!resourceBundleBaseNames.contains(bundleBaseName)) if (!resourceBundleBaseNames.contains(bundleBaseName))
{ {
messageService.registerResourceBundle(storeRef.toString() + repositoryLocation.getPath() + bundleBaseName);
resourceBundleBaseNames.add(bundleBaseName); resourceBundleBaseNames.add(bundleBaseName);
} }
} }

View File

@@ -31,17 +31,18 @@ import java.io.PrintWriter;
import java.io.Serializable; import java.io.Serializable;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.NamespaceDAO; import org.alfresco.repo.dictionary.NamespaceDAO;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.repo.node.db.DbNodeServiceImpl;
import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -50,6 +51,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti
import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
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.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -67,6 +69,8 @@ import org.apache.commons.logging.LogFactory;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import junit.framework.TestCase;
/** /**
* @see RepoAdminServiceImpl * @see RepoAdminServiceImpl
* *
@@ -88,6 +92,7 @@ public class RepoAdminServiceImplTest extends TestCase
private NamespaceService namespaceService; private NamespaceService namespaceService;
private BehaviourFilter behaviourFilter; private BehaviourFilter behaviourFilter;
private DictionaryDAO dictionaryDAO; private DictionaryDAO dictionaryDAO;
private MessageService messageService;
final String modelPrefix = "model-"; final String modelPrefix = "model-";
final static String MKR = "{MKR}"; final static String MKR = "{MKR}";
@@ -140,6 +145,7 @@ public class RepoAdminServiceImplTest extends TestCase
namespaceService = (NamespaceService) ctx.getBean("NamespaceService"); namespaceService = (NamespaceService) ctx.getBean("NamespaceService");
behaviourFilter = (BehaviourFilter)ctx.getBean("policyBehaviourFilter"); behaviourFilter = (BehaviourFilter)ctx.getBean("policyBehaviourFilter");
dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO");
messageService = (MessageService) ctx.getBean("MessageService");
DbNodeServiceImpl dbNodeService = (DbNodeServiceImpl)ctx.getBean("dbNodeService"); DbNodeServiceImpl dbNodeService = (DbNodeServiceImpl)ctx.getBean("dbNodeService");
dbNodeService.setEnableTimestampPropagation(false); dbNodeService.setEnableTimestampPropagation(false);
@@ -866,7 +872,181 @@ public class RepoAdminServiceImplTest extends TestCase
} }
} }
// // Test deploy bundle from classpath
// TODO - Test custom message management public void testDeployMessageBundleFromClasspath() throws Exception
// {
String bundleBaseName = "mycustommessages";
String resourceClasspath = "alfresco/extension/messages/" + bundleBaseName;
final String message_key = "mycustommessages.key1";
final String message_value_default = "This is a custom message";
final String message_value_fr = "Ceci est un message personnalis\\u00e9";
final String message_value_de = "Dies ist eine benutzerdefinierte Nachricht";
// Undeploy the bundle
if (repoAdminService.getMessageBundles().contains(bundleBaseName))
{
repoAdminService.undeployMessageBundle(bundleBaseName);
}
// Verify the custom bundle is registered
assertFalse("The custom bundle should not be deployed", repoAdminService.getMessageBundles().contains(bundleBaseName));
// Depoly the message bundle
repoAdminService.deployMessageBundle(resourceClasspath);
// Reload the messages
repoAdminService.reloadMessageBundle(bundleBaseName);
// Verify the custom bundle is registered
assertTrue("The custom bundle should be deployed", repoAdminService.getMessageBundles().contains(bundleBaseName));
// Verify we have the messages for each locale
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
assertMessageValue("Cannot retrieve default message value", message_value_default, message_key, Locale.getDefault());
assertMessageValue("Cannot retrieve french message value", message_value_fr, message_key, Locale.FRANCE);
assertMessageValue("Cannot retrieve german message value", message_value_de, message_key, Locale.GERMANY);
// Test deploy a non existent bundle
try
{
repoAdminService.deployMessageBundle("alfresco/extension/messages/inexistentbundle");
fail("Bundle was not supposed to be deployed");
}
catch (Exception e)
{
// Expected to fail
}
}
// Test deploy bundle from repo and reload bundles
public void testDeployMessageBundleFromRepo() throws Exception
{
final String bundleBaseName = "repoBundle";
final String message_key = "repoBundle.key1";
final String message_value = "Value 1";
final String message_value_fr = "Value FR";
final String message_value_de = "Value DE";
final String message_value_new = "New Value 1";
// Set location
NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
List<NodeRef> nodeRefs = searchService.selectNodes(rootNodeRef, "/app:company_home/app:dictionary/app:messages", null,
namespaceService, false);
assertEquals(1, nodeRefs.size());
NodeRef messagesNodeRef = nodeRefs.get(0);
// Clear messages of this bundle if they exist and are registered
clearRepoBundles(messagesNodeRef);
assertEquals(0, repoAdminService.getMessageBundles().size());
// Create and upload the message files
NodeRef messageNode_default = createMessagesNodeWithSingleKey(messagesNodeRef, bundleBaseName, message_key, null,
message_value);
createMessagesNodeWithSingleKey(messagesNodeRef, bundleBaseName, message_key, Locale.FRANCE.toString(), message_value_fr);
createMessagesNodeWithSingleKey(messagesNodeRef, bundleBaseName, message_key, Locale.GERMANY.toString(),
message_value_de);
// Reload the messages
repoAdminService.reloadMessageBundle(bundleBaseName);
// Verify we have the messages for each locale
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
assertMessageValue("Cannot retrieve default message value", message_value, message_key, Locale.getDefault());
assertMessageValue("Cannot retrieve french message value", message_value_fr, message_key, Locale.FRANCE);
assertMessageValue("Cannot retrieve german message value", message_value_de, message_key, Locale.GERMANY);
// Change the values
putContentInMessageNode(messageNode_default, message_key, message_value_new);
// Verify we still have the old value
assertMessageValue("Unexpected change of message value", message_value, message_key, Locale.getDefault());
// Reload the messages
repoAdminService.reloadMessageBundle(bundleBaseName);
// Verify new values
assertMessageValue("Change of message value not reflected", message_value_new, message_key, Locale.getDefault());
}
/**
* Create messages node
*/
private NodeRef createMessagesNodeWithSingleKey(NodeRef parentNode, String bundleName, String key, String locale,
String localeValue)
{
String msg_extension = ".properties";
String filename = bundleName + msg_extension;
String messageValue = localeValue;
if (locale != null)
{
filename = bundleName + "_" + locale + msg_extension;
}
// Create a model node
NodeRef messageNode = this.nodeService.createNode(
parentNode,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, filename),
ContentModel.TYPE_CONTENT,
Collections.<QName, Serializable> singletonMap(ContentModel.PROP_NAME, filename)
).getChildRef();
putContentInMessageNode(messageNode, key, messageValue);
return messageNode;
}
/**
* Write content of message node
*/
private void putContentInMessageNode(NodeRef nodeRef, String key, String value)
{
ContentWriter contentWriter = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
contentWriter.setEncoding("UTF-8");
contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
String messagesString = key + "=" + value;
contentWriter.putContent(messagesString);
}
/**
* Clear Repo Bundle
*/
private void clearRepoBundles(NodeRef parentNode)
{
List<String> repoBundles = repoAdminService.getMessageBundles();
for (String repoBundle : repoBundles)
{
repoAdminService.undeployMessageBundle(repoBundle);
}
List<ChildAssociationRef> messageNodes = nodeService.getChildAssocs(parentNode);
for (ChildAssociationRef messageChildRef : messageNodes)
{
NodeRef messageNode = messageChildRef.getChildRef();
nodeService.deleteNode(messageNode);
}
}
/**
* Clear Repo Bundle
*/
private void assertMessageValue(String errorMessage, String expectedValue, String key, Locale locale)
{
transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
assertEquals(errorMessage, expectedValue, messageService.getMessage(key, locale));
return null;
}
});
}
} }

View File

@@ -25,9 +25,14 @@
*/ */
package org.alfresco.repo.dictionary; package org.alfresco.repo.dictionary;
import java.io.Serializable;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.transaction.UserTransaction;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
@@ -53,8 +58,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import javax.transaction.UserTransaction;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Category(BaseSpringTestsCategory.class) @Category(BaseSpringTestsCategory.class)
@@ -94,6 +97,14 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
"</model>"; "</model>";
public static final String MESSAGES_KEY = "my_bootstrap_test";
public static final String MESSAGES_VALUE = "My Message";
public static final String MESSAGES_VALUE_FR = "Mon message";
public static final String FOLDERNAME_MODELS = "models";
public static final String FOLDERNAME_MESSAGES = "messages";
public static final String BUNDLENAME_MESSAGES = "testBootstap";
public static final String FILENAME_MESSAGES_EXT = ".properties";
/** Behaviour filter */ /** Behaviour filter */
private BehaviourFilter behaviourFilter; private BehaviourFilter behaviourFilter;
@@ -123,7 +134,8 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
private UserTransaction txn; private UserTransaction txn;
private StoreRef storeRef; private StoreRef storeRef;
private NodeRef rootNodeRef; private NodeRef rootModelsNodeRef;
private NodeRef rootMessagesNodeRef;
@Before @Before
public void before() throws Exception public void before() throws Exception
@@ -151,7 +163,17 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
// Create the store and get the root node // Create the store and get the root node
this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis());
this.rootNodeRef = this.nodeService.getRootNode(this.storeRef);
NodeRef rootNodeRef = this.nodeService.getRootNode(this.storeRef);
this.rootModelsNodeRef = this.nodeService
.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, FOLDERNAME_MODELS), ContentModel.TYPE_FOLDER)
.getChildRef();
this.rootMessagesNodeRef = this.nodeService
.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, FOLDERNAME_MESSAGES), ContentModel.TYPE_FOLDER)
.getChildRef();
txn.commit(); txn.commit();
@@ -168,16 +190,18 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
this.bootstrap.setMessageService(this.messageService); this.bootstrap.setMessageService(this.messageService);
this.bootstrap.setPolicyComponent(this.policyComponent); this.bootstrap.setPolicyComponent(this.policyComponent);
RepositoryLocation location = new RepositoryLocation(); RepositoryLocation modelsLocation = new RepositoryLocation(this.storeRef,
location.setStoreProtocol(this.storeRef.getProtocol()); this.nodeService.getPath(rootModelsNodeRef).toPrefixString(namespaceService), RepositoryLocation.LANGUAGE_PATH);
location.setStoreId(this.storeRef.getIdentifier()); RepositoryLocation messagesLocation = new RepositoryLocation(this.storeRef,
location.setQueryLanguage(RepositoryLocation.LANGUAGE_PATH); this.nodeService.getPath(rootMessagesNodeRef).toPrefixString(namespaceService), RepositoryLocation.LANGUAGE_PATH);
// NOTE: we are not setting the path for now .. in doing so we are searching the root node only
List<RepositoryLocation> locations = new ArrayList<RepositoryLocation>(); List<RepositoryLocation> modelsLocations = new ArrayList<RepositoryLocation>();
locations.add(location); modelsLocations.add(modelsLocation);
List<RepositoryLocation> messagesLocations = new ArrayList<RepositoryLocation>();
messagesLocations.add(messagesLocation);
this.bootstrap.setRepositoryModelsLocations(locations); this.bootstrap.setRepositoryModelsLocations(modelsLocations);
this.bootstrap.setRepositoryMessagesLocations(messagesLocations);
// register with dictionary service // register with dictionary service
this.bootstrap.register(); this.bootstrap.register();
@@ -225,6 +249,14 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
"base1", "base1",
"prop1"); "prop1");
// Create a message file for the default locale
NodeRef messageNodeDefaultLoc = createMessagesNode(null, null);
// Create a message file for the french locale
createMessagesNode(Locale.FRANCE.toString(), MESSAGES_VALUE_FR);
// Construct baseBundleName for validation
String baseBundleName = storeRef.toString()
+ messageService.getBaseBundleName(nodeService.getPath(messageNodeDefaultLoc).toPrefixString(namespaceService));
// Check that the model is not in the dictionary yet // Check that the model is not in the dictionary yet
try try
{ {
@@ -251,6 +283,12 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
QName.createQName("http://www.alfresco.org/model/test3DictionaryBootstrapFromRepo/1.0", "testModel3")); QName.createQName("http://www.alfresco.org/model/test3DictionaryBootstrapFromRepo/1.0", "testModel3"));
assertNotNull(modelDefinition3); assertNotNull(modelDefinition3);
// Check if the messages were registered correctly
assertTrue("The message bundle should be registered", messageService.getRegisteredBundles().contains(baseBundleName));
assertEquals("The default message value is not as expected", MESSAGES_VALUE, messageService.getMessage(MESSAGES_KEY));
assertEquals("The message value in french is not as expected", MESSAGES_VALUE_FR,
messageService.getMessage(MESSAGES_KEY, Locale.FRANCE));
txn.commit(); txn.commit();
} }
@@ -277,7 +315,7 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
{ {
// Create a model node // Create a model node
NodeRef model = this.nodeService.createNode( NodeRef model = this.nodeService.createNode(
this.rootNodeRef, this.rootModelsNodeRef,
ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN,
QName.createQName("{test}models"), QName.createQName("{test}models"),
ContentModel.TYPE_DICTIONARY_MODEL).getChildRef(); ContentModel.TYPE_DICTIONARY_MODEL).getChildRef();
@@ -300,6 +338,39 @@ public class DictionaryRepositoryBootstrapTest extends BaseSpringTest
return model; return model;
} }
/**
* Create messages node
*
* @return NodeRef
*/
private NodeRef createMessagesNode(String locale, String localeValue)
{
String filename = BUNDLENAME_MESSAGES + FILENAME_MESSAGES_EXT;
String messageValue = MESSAGES_VALUE;
if (locale != null)
{
filename = BUNDLENAME_MESSAGES + "_" + locale + FILENAME_MESSAGES_EXT;
messageValue = localeValue;
}
// Create a model node
NodeRef messageNode = this.nodeService.createNode(
this.rootMessagesNodeRef,
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, filename),
ContentModel.TYPE_CONTENT,
Collections.<QName, Serializable> singletonMap(ContentModel.PROP_NAME, filename)
).getChildRef();
ContentWriter contentWriter = this.contentService.getWriter(messageNode, ContentModel.PROP_CONTENT, true);
contentWriter.setEncoding("UTF-8");
contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
String messagesString = MESSAGES_KEY + "=" + messageValue;
contentWriter.putContent(messagesString);
return messageNode;
}
/** /**
* *
* Gets the model string * Gets the model string

View File

@@ -0,0 +1 @@
mycustommessages.key1=This is a custom message

View File

@@ -0,0 +1 @@
mycustommessages.key1=Dies ist eine benutzerdefinierte Nachricht

View File

@@ -0,0 +1 @@
mycustommessages.key1=Ceci est un message personnalis\u00e9