(listSize);
+
+ // construct the languages map and list
+ for (ConfigElement langElem : languages)
+ {
+ String code = convertToOldISOCode(langElem.getAttribute(ATTR_CODE));
+ String order = langElem.getAttribute(ATTR_ORDER);
+ String value = langElem.getValue();
+ String def = langElem.getAttribute(ATTR_DEFAULT);
+
+ orderedLangCodes.add(Integer.parseInt(order) - 1, code);
+
+ languagesByCode.put(code, value);
+
+ if(def != null && Boolean.parseBoolean(def))
+ {
+ if(defaultLanguage != null)
+ {
+ logger.warn("Ignoring default attribute is not unique le last matched will be used");
+ }
+
+ this.defaultLanguage = code;
+ }
+ }
+
+ // make the collections read-only
+ this.orderedLangCodes = Collections.unmodifiableList(this.orderedLangCodes);
+ this.languagesByCode = Collections.unmodifiableMap(this.languagesByCode);
+ }
+
+ /* (non-Javadoc)
+ * @see org.alfresco.service.cmr.ml.ContentFilterLanguagesService#convertToOldISOCode(java.lang.String)
+ */
+ public String convertToOldISOCode(String code)
+ {
+ if(code.equalsIgnoreCase("he"))
+ code = "iw";
+ else if(code.equalsIgnoreCase("id"))
+ code = "in";
+ else if(code.equalsIgnoreCase("yi"))
+ code = "ji";
+
+ return code;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.alfresco.service.cmr.ml.ContentFilterLanguagesService#convertToNewISOCode(java.lang.String)
+ */
+ public String convertToNewISOCode(String code) {
+
+ if(code.equalsIgnoreCase("iw"))
+ code = "he";
+ else if(code.equalsIgnoreCase("in"))
+ code = "id";
+ else if(code.equalsIgnoreCase("ji"))
+ code = "yi";
+
+ return code;
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java
new file mode 100644
index 0000000000..f80ae6c59a
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.model.ml;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.content.ContentServicePolicies;
+import org.alfresco.repo.copy.CopyServicePolicies;
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.repo.policy.PolicyScope;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Class containing behaviour for the multilingual empty translation aspect.
+ * An empty translation is a document's properties translation. This kind of node
+ * doesn't have a content.
+ *
+ * {@link ContentModel#ASPECT_MULTILINGUAL_EMPTY_TRANSLATION ml empty document aspect}
+ *
+ * @author yanipig
+ */
+public class EmptyTranslationAspect implements
+ CopyServicePolicies.OnCopyNodePolicy,
+ NodeServicePolicies.BeforeDeleteNodePolicy,
+ NodeServicePolicies.OnRemoveAspectPolicy,
+ ContentServicePolicies.OnContentUpdatePolicy
+{
+
+ // Dependencies
+ private PolicyComponent policyComponent;
+
+ private NodeService nodeService;
+
+
+ /**
+ * Initialise the Multilingual Empty Translation Aspect
+ *
+ * Ensures that the {@link ContentModel#ASPECT_MULTILINGUAL_EMPTY_TRANSLATION ml empty document aspect}
+ */
+ public void init()
+ {
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"),
+ ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
+ new JavaBehaviour(this, "onCopyNode"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"),
+ ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
+ new JavaBehaviour(this, "onContentUpdate"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
+ ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
+ new JavaBehaviour(this, "onRemoveAspect"));
+ }
+
+ /**
+ * @param policyComponent the policy component to register behaviour with
+ */
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+
+ /**
+ * @param nodeService the Node Service to set
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * Copy a cm:mlEmptyTranslation is not permit.
+ *
+ * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.StoreRef, boolean, org.alfresco.repo.policy.PolicyScope)
+ */
+ public void onCopyNode(QName classRef, NodeRef sourceNodeRef, StoreRef destinationStoreRef, boolean copyToNewNode, PolicyScope copyDetails)
+ {
+ throw new IllegalStateException("It's impossible to copy an empty translation");
+ }
+
+ /**
+ * If a content is added to a cm:mlEmptyTranslation, remove this aspect.
+ *
+ * @see org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy#onContentUpdate(org.alfresco.service.cmr.repository.NodeRef, boolean)
+ */
+ public void onContentUpdate(NodeRef nodeRef, boolean newContent)
+ {
+ if(newContent)
+ {
+ nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
+ }
+ }
+
+ /**
+ * If a cm:mlEmptyTranslation is deleted, it can't be archived.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public void beforeDeleteNode(NodeRef nodeRef)
+ {
+ // add TEMPORARY ASPECT to force the deleteNode
+ nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);
+ }
+
+ /**
+ * If the aspect cm:mlEmptyTranslation is removed and the content url property is null, the node can't be store much time.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) *
+ */
+ public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
+ {
+ // if the node is updated, the URL will not be null and the rome aspect don't delete
+ // the translation. It will be a simple mlDocument.
+ if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)
+ && ((ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT)).getContentUrl() == null)
+ {
+ // add TEMPORARY ASPECT to force the deleteNode
+ nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);
+ nodeService.deleteNode(nodeRef);
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/MLContainerType.java b/source/java/org/alfresco/repo/model/ml/MLContainerType.java
new file mode 100644
index 0000000000..6be5a0a55d
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/MLContainerType.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.model.ml;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Class containing behaviour for the multilingual multilingual container type.
+ * A multilingual container type is fonctionally named 'Logical Document'.
+ * It links each translation of a semantical message together
+ *
+ * {@link ContentModel#TYPE_MULTILINGUAL_CONTAINER multilingual container type}
+ *
+ * @author yanipig
+ */
+public class MLContainerType implements
+ NodeServicePolicies.BeforeDeleteNodePolicy,
+ NodeServicePolicies.OnUpdatePropertiesPolicy
+{
+ /**
+ * set a property with this QName when the deletion process is running on the mlContainer. In this case
+ * the policies of the remove aspect of mlDocument don't delete one more time the mlContainer.
+ */
+ /*package*/ static QName PROP_NAME_DELETION_RUNNING = QName.createQName("__MLContanier_", "Deletion_Running");
+
+ // Dependencies
+ private PolicyComponent policyComponent;
+
+ private NodeService nodeService;
+
+ private MultilingualContentService multilingualContentService;
+
+ /**
+ * Initialise the Multilingual Container Type
+ *
+ * Ensures that the {@link ContentModel#ASPECT_MULTILINGUAL_EMPTY_TRANSLATION ml empty document aspect}
+ */
+ public void init()
+ {
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
+ ContentModel.TYPE_MULTILINGUAL_CONTAINER,
+ new JavaBehaviour(this, "onUpdateProperties"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ ContentModel.TYPE_MULTILINGUAL_CONTAINER,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+ }
+
+ /**
+ * @param policyComponent the policy component to register behaviour with
+ */
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+ /**
+ * @param multilingualContentService the Multilingual Content Service to set
+ */
+ public void setMultilingualContentService(
+ MultilingualContentService multilingualContentService)
+ {
+ this.multilingualContentService = multilingualContentService;
+ }
+
+ /**
+ * @param nodeService the Node Service to set
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * If a cm:mlContainer is deleted, it can't be archived.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public void beforeDeleteNode(NodeRef nodeRef)
+ {
+ Map translations = multilingualContentService.getTranslations(nodeRef);
+
+ // add the DELETION_RUNNING property
+ nodeService.setProperty(nodeRef, PROP_NAME_DELETION_RUNNING, new Boolean(true));
+
+ for(Map.Entry entry : translations.entrySet())
+ {
+ nodeService.removeAspect(entry.getValue(), ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
+ }
+
+ // remove the DELETION_RUNNING property
+ Map prop = nodeService.getProperties(nodeRef);
+ prop.remove(PROP_NAME_DELETION_RUNNING);
+ nodeService.setProperties(nodeRef, prop);
+
+ }
+
+ /**
+ * The property locale of a cm:mlContainer represents the locale of the pivot translation.
+ *
+ * Since the pivot must be an existing translation and the pivot can t be empty, some tests must be performed when
+ * the locale of the mlContainer is updated.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map)
+ */
+ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after)
+ {
+ Locale localeAfter = (Locale) after.get(ContentModel.PROP_LOCALE);
+ Locale localeBefore = (Locale) before.get(ContentModel.PROP_LOCALE);
+
+ // The locale can be set as null if the container have no children.
+ // Normaly, it's ONLY the case at the creation of the container.
+ if(localeAfter == null && nodeService.getChildAssocs(nodeRef).size() != 0)
+ {
+ throw new IllegalArgumentException("A Locale property must be defined for a Multilingual Container and can't be null");
+ }
+
+ if(localeAfter != null && !localeAfter.equals(localeBefore))
+ {
+ Map translations = multilingualContentService.getTranslations(nodeRef);
+
+ //get the new pivot translation
+ NodeRef pivot = translations.get(localeAfter);
+
+ if(pivot == null)
+ {
+ throw new IllegalArgumentException("The pivot translation must be an existing translation");
+ }
+ else if(nodeService.hasAspect(pivot, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
+ {
+ throw new IllegalArgumentException("The pivot translation can't be an empty translation");
+ }
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
index 240c113c1e..2660cda30a 100644
--- a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
@@ -15,15 +15,17 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.model.ml;
+import java.io.Serializable;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -33,8 +35,12 @@ import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.i18n.I18NUtil;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.version.VersionModel;
+import org.alfresco.service.cmr.ml.ContentFilterLanguagesService;
import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -52,19 +58,22 @@ import org.apache.commons.logging.LogFactory;
/**
* Multilingual support implementation
- *
+ *
* @author Derek Hulley
* @author Philippe Dubois
+ * @author yanipig
*/
public class MultilingualContentServiceImpl implements MultilingualContentService
{
private static Log logger = LogFactory.getLog(MultilingualContentServiceImpl.class);
-
+
private NodeService nodeService;
private SearchService searchService;
private VersionService versionService;
private SearchParameters searchParametersMLRoot;
-
+ private ContentFilterLanguagesService contentFilterLanguagesService;
+ private FileFolderService fileFolderService;
+
public MultilingualContentServiceImpl()
{
searchParametersMLRoot = new SearchParameters();
@@ -73,27 +82,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
searchParametersMLRoot.addStore(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"));
searchParametersMLRoot.setQuery("/cm:multilingualRoot");
}
-
- public void setNodeService(NodeService nodeService)
- {
- this.nodeService = nodeService;
- }
- public void setSearchService(SearchService searchService)
- {
- this.searchService = searchService;
- }
-
- public void setVersionService(VersionService versionService)
- {
- this.versionService = versionService;
- }
-
- public void renameWithMLExtension(NodeRef translationNodeRef)
- {
- throw new UnsupportedOperationException();
- }
-
/**
* @return Returns a reference to the node that will hold all the cm:mlContainer nodes.
*/
@@ -119,7 +108,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
rs.close();
}
}
-
+
private static final QName QNAME_ML_CONTAINER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mlContainer");
private static final QName QNAME_ML_TRANSLATION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mlTranslation");
/**
@@ -137,11 +126,11 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
// done
return assocRef.getChildRef();
}
-
+
/**
* Retrieve or create a cm:mlDocument container for the given node, which must have the
* cm:mlDocument already applied.
- *
+ *
* @param mlDocumentNodeRef an existing cm:mlDocument
* @param allowCreate true if a cm:mlContainer must be created if on doesn't exist,
* otherwise false if a parent cm:mlContainer is expected to exist.
@@ -225,12 +214,15 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
// The aspect is present, so just ensure that the locale is correct
nodeService.setProperty(contentNodeRef, ContentModel.PROP_LOCALE, locale);
}
-
+
// Do we make use of an existing container?
if (mlContainerNodeRef == null)
{
// Make one
mlContainerNodeRef = getOrCreateMLContainer(contentNodeRef, true);
+
+ // set the pivot language
+ nodeService.setProperty(mlContainerNodeRef, ContentModel.PROP_LOCALE, locale);
}
else
{
@@ -240,7 +232,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
{
throw new AlfrescoRuntimeException("Duplicate locale in document pool: " + locale);
}
-
+
// Use the existing container
nodeService.addChild(
mlContainerNodeRef,
@@ -248,6 +240,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
ContentModel.ASSOC_MULTILINGUAL_CHILD,
QNAME_ML_TRANSLATION);
}
+
// done
return mlContainerNodeRef;
}
@@ -266,7 +259,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
}
return mlContainerNodeRef;
}
-
+
/** @inheritDoc */
public NodeRef addTranslation(NodeRef newTranslationNodeRef, NodeRef translationOfNodeRef, Locale locale)
{
@@ -307,7 +300,7 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
/** @inheritDoc */
public void createEdition( NodeRef translationNodeRef)
{
- NodeRef mlContainerNodeRef = getOrCreateMLContainer(translationNodeRef, false);
+ NodeRef mlContainerNodeRef = getOrCreateMLContainer(translationNodeRef, false);
// Ensure that the translation given is one of the children
getOrCreateMLContainer(translationNodeRef, false);
// Get all the container's children
@@ -315,8 +308,25 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
mlContainerNodeRef,
ContentModel.ASSOC_MULTILINGUAL_CHILD,
RegexQNamePattern.MATCH_ALL);
- // Version the container and all its children
- versionService.createVersion(mlContainerNodeRef, null, true);
+
+
+ // Get and store the translation verions associated to the mlContainer
+ List versions = new ArrayList(childAssocRefs.size());
+
+ for (ChildAssociationRef childAssoc : childAssocRefs)
+ {
+ versions.add(versionService.getCurrentVersion(childAssoc.getChildRef()));
+ }
+
+ Map editionProperties = new HashMap();
+ editionProperties.put(
+ VersionModel.PROP_QNAME_TRANSLATION_VERIONS.toString(),
+ (Serializable) versions
+ );
+
+ // Version the container and all its children
+ versionService.createVersion(mlContainerNodeRef, editionProperties, true);
+
// Remove all the child documents apart from the given node
boolean found = false;
for (ChildAssociationRef childAssoc : childAssocRefs)
@@ -416,4 +426,173 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
}
return nearestNodeRef;
}
-}
+
+ /** @inheritDoc */
+ public List getMissingTranslations(NodeRef localizedNodeRef, boolean addThisNodeLocale)
+ {
+ List foundLocales = new ArrayList(getTranslations(localizedNodeRef).keySet());
+ List foundLanguages = new ArrayList();
+
+
+ // transform locales into languages codes
+ for(Locale locale : foundLocales)
+ {
+ foundLanguages.add(locale.getLanguage());
+ }
+
+ // add the locale of the given node if required
+ if(addThisNodeLocale)
+ {
+ Locale localeNode = (Locale) nodeService.getProperty(localizedNodeRef, ContentModel.PROP_LOCALE);
+
+ if(localeNode != null)
+ {
+ foundLanguages.remove(localeNode.toString());
+ }
+ else
+ {
+ logger.warn("No locale found for the node " + localizedNodeRef);
+ }
+ }
+
+ List missingLanguages = null;
+
+ if(foundLanguages.size() == 0)
+ {
+ // The given node is the only one available translation and it must
+ // be return.
+ // MissingLanguages become the entire list pf languages.
+ missingLanguages = contentFilterLanguagesService.getFilterLanguages();
+ }
+ else
+ {
+ // get the missing languages form the list of content filter languages
+ missingLanguages = contentFilterLanguagesService.getMissingLanguages(foundLanguages);
+ }
+ // construct a list of locales
+ List missingLocales = new ArrayList(missingLanguages.size() + 1);
+
+ for(String lang : missingLanguages)
+ {
+ missingLocales.add(I18NUtil.parseLocale(lang));
+ }
+
+ return missingLocales;
+ }
+
+ /** @inheritDoc */
+ public NodeRef getPivotTranslation(NodeRef nodeRef)
+ {
+ if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ NodeRef container = getTranslationContainer(nodeRef);
+ Locale containerLocale = (Locale) nodeService.getProperty(container, ContentModel.PROP_LOCALE);
+
+ return getTranslationForLocale(nodeRef, containerLocale);
+ }
+ else if(ContentModel.TYPE_MULTILINGUAL_CONTAINER.equals(nodeService.getType(nodeRef)))
+ {
+ Locale containerLocale = (Locale) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCALE);
+
+ return getTranslationForLocale(getTranslations(nodeRef).get(containerLocale), containerLocale);
+ }
+ else
+ {
+ logger.warn("The node is not multilingual " + nodeRef);
+
+ return null;
+ }
+ }
+
+ /**
+ * @inheritDoc */
+ public NodeRef addEmptyTranslation(NodeRef translationOfNodeRef, String name, Locale locale)
+ {
+ // any node used as reference
+ NodeRef anyTranslation;
+ // the empty document to create
+ NodeRef newTranslationNodeRef = null;
+
+ QName typeQName = nodeService.getType(translationOfNodeRef);
+ if (typeQName.equals(ContentModel.TYPE_MULTILINGUAL_CONTAINER))
+ {
+ // Set the ml container ans get the pivot
+ anyTranslation = getPivotTranslation(translationOfNodeRef);
+ }
+ else if(nodeService.hasAspect(translationOfNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ anyTranslation = translationOfNodeRef;
+ }
+ else
+ {
+ throw new IllegalArgumentException(
+ "Node must have aspect " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " applied or be a " + ContentModel.TYPE_MULTILINGUAL_CONTAINER);
+ }
+
+ // Create the document in the space of the node of reference
+ NodeRef parentNodeRef = nodeService.getPrimaryParent(anyTranslation).getParentRef();
+
+ newTranslationNodeRef = fileFolderService.create(
+ parentNodeRef,
+ name,
+ ContentModel.TYPE_CONTENT).getNodeRef();
+
+ // add the translation to the container
+ addTranslation(newTranslationNodeRef, translationOfNodeRef, locale);
+
+ // set it empty
+ nodeService.addAspect(newTranslationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, null);
+
+ // get the extension and set the ContentData property with an null URL.
+ String extension = "";
+ int dotIdx;
+ if((dotIdx = name.lastIndexOf(".")) > -1 )
+ {
+ extension = name.substring(dotIdx);
+ }
+
+ nodeService.setProperty(newTranslationNodeRef, ContentModel.PROP_CONTENT,
+ new ContentData(null, extension, 0, "UTF-8", locale));
+
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Added an empty translation: \n" +
+ " Translation of: " + translationOfNodeRef + " of type " + typeQName + "\n" +
+ " New translation: " + newTranslationNodeRef + "\n" +
+ " Locale: " + locale);
+ }
+
+ return newTranslationNodeRef;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setSearchService(SearchService searchService)
+ {
+ this.searchService = searchService;
+ }
+
+ public void setVersionService(VersionService versionService)
+ {
+ this.versionService = versionService;
+ }
+
+ public void setContentFilterLanguagesService(ContentFilterLanguagesService contentFilterLanguagesService)
+ {
+ this.contentFilterLanguagesService = contentFilterLanguagesService;
+ }
+
+ public void setFileFolderService(FileFolderService fileFolderService)
+ {
+ this.fileFolderService = fileFolderService;
+ }
+
+ public void renameWithMLExtension(NodeRef translationNodeRef)
+ {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java
new file mode 100644
index 0000000000..846c8ed10b
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Map;
+
+import org.alfresco.i18n.I18NUtil;
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.copy.CopyServicePolicies;
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.repo.policy.PolicyScope;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+
+
+/**
+ * Class containing behaviour for the multilingual document aspect.
+ *
+ * {@link ContentModel#ASPECT_MULTILINGUAL_DOCUMENT ml document aspect}
+ *
+ * @author yanipig
+ */
+public class MultilingualDocumentAspect implements
+ CopyServicePolicies.OnCopyNodePolicy,
+ CopyServicePolicies.OnCopyCompletePolicy,
+ NodeServicePolicies.BeforeDeleteNodePolicy,
+ NodeServicePolicies.BeforeRemoveAspectPolicy,
+ NodeServicePolicies.OnRemoveAspectPolicy,
+ NodeServicePolicies.OnUpdatePropertiesPolicy
+{
+
+ // Dependencies
+ private PolicyComponent policyComponent;
+
+ private MultilingualContentService multilingualContentService;
+
+ private NodeService nodeService;
+
+
+ /**
+ * Initialise the Multilingual Aspect
+ *
+ * Ensures that the {@link ContentModel#ASPECT_MULTILINGUAL_DOCUMENT ml document aspect}
+ */
+ public void init()
+ {
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "onCopyNode"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeRemoveAspect"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "beforeRemoveAspect"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "onUpdateProperties"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "onCopyComplete"));
+
+ this.policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
+ ContentModel.ASPECT_MULTILINGUAL_DOCUMENT,
+ new JavaBehaviour(this, "onRemoveAspect"));
+
+ }
+
+ /**
+ * @param policyComponent the policy component to register behaviour with
+ */
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+ /**
+ * @param multilingualContentService the Multilingual Content Service to set
+ */
+ public void setMultilingualContentService(
+ MultilingualContentService multilingualContentService)
+ {
+ this.multilingualContentService = multilingualContentService;
+ }
+
+ /**
+ * @param nodeService the Node Service to set
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * The copy of a cm:mlDocument can't keep the Multilingual aspect.
+ *
+ * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.StoreRef, boolean, org.alfresco.repo.policy.PolicyScope)
+ */
+ public void onCopyNode(QName classRef, NodeRef sourceNodeRef, StoreRef destinationStoreRef, boolean copyToNewNode, PolicyScope copyDetails)
+ {
+ copyDetails.removeAspect(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
+ }
+
+ /**
+ * The copy of mlDocument don't keep the 'locale' property.
+ *
+ * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyCompletePolicy#onCopyComplete(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, boolean, java.util.Map)
+ */
+ public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef destinationRef, boolean copyToNewNode, Map copyMap)
+ {
+ Map copiedProp = nodeService.getProperties(destinationRef);
+ copiedProp.remove(ContentModel.PROP_LOCALE);
+ nodeService.setProperties(destinationRef, copiedProp);
+ }
+
+ /**
+ * A cm:mlDocument is pivot translation it is a multilingual document (non empty) if it's language matches the language
+ * of its cm:mlDocument. And a pivot translation can't be removed if it's not the last translation of the cm:mlContainer.
+ *
+ * If a translation is deleted, it's multilingual aspect is lost.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public void beforeDeleteNode(NodeRef nodeRef)
+ {
+ nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
+ }
+
+ /**
+ * When a pivot translation is deleted, it's cm:mlContainer is deleted too.
+ *
+ * Note: When the pivot translation and the mlContainer are deleted, the mlDocument apsect is removed from
+ * the other translations. It will be better to don't allow the deletion of the pivot if other translation is
+ * available at the client side level.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.BeforeRemoveAspectPolicy#beforeRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
+ */
+ public void beforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
+ {
+ if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ NodeRef mlContainer = multilingualContentService.getTranslationContainer(nodeRef);
+
+ // nothing to do if the mlContainer is in a deletion process
+ Boolean inDeleteProcess = (Boolean) nodeService.getProperty(mlContainer, MLContainerType.PROP_NAME_DELETION_RUNNING);
+ if(inDeleteProcess != null && inDeleteProcess == true)
+ {
+ return;
+ }
+
+ Locale mlContainerLocale = (Locale) nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE);
+ Locale nodeRefLocale = (Locale) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCALE);
+
+ nodeService.removeChild(mlContainer, nodeRef);
+
+ // if last translation or if nodeRef is pivot translation
+ if(multilingualContentService.getTranslations(mlContainer).size() == 0
+ || mlContainerLocale.equals(nodeRefLocale))
+ {
+ // delete the mlContainer
+ nodeService.deleteNode(mlContainer);
+ }
+ }
+ }
+
+ /**
+ * After removing the cm:mlDocument aspect :
+ * - the node is removed is it's a cm:mlEmptyTranslation
+ * - if not, only the locale property is removed
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
+ */
+ public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
+ {
+ if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+
+ if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
+ {
+ // note: if the node has the temporary aspect and the mlEmptyTranslation in a same
+ // time, it means that the node is being deleted by another process...
+ if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY))
+ {
+ nodeService.deleteNode(nodeRef);
+ }
+ }
+ else
+ {
+ Map props = nodeService.getProperties(nodeRef);
+ props.remove(ContentModel.PROP_LOCALE);
+ nodeService.setProperties(nodeRef, props);
+
+ }
+ }
+ }
+
+ /**
+ * Ensure that the locale is unique inside the mlContainer.
+ *
+ * If the locale of a pivot translation is modified, the pivot locale reference of the mlContainer
+ * must be modified too.
+ *
+ * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map)
+ */
+ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after)
+ {
+ Locale localeBefore = (Locale) before.get(ContentModel.PROP_LOCALE);
+ Locale localeAfter;
+
+ // the after local property type can be either Locale or String
+ Serializable objLocaleAfter = after.get(ContentModel.PROP_LOCALE);
+ if (objLocaleAfter instanceof Locale )
+ {
+ localeAfter = (Locale) objLocaleAfter;
+
+ }
+ else
+ {
+ localeAfter = I18NUtil.parseLocale(objLocaleAfter.toString());
+ }
+
+ // if the local has been modified
+ if(!localeBefore.equals(localeAfter))
+ {
+ NodeRef mlContainer = multilingualContentService.getTranslationContainer(nodeRef);
+
+ // Since the map returned by the getTranslations doesn't duplicate keys, the size of this map will be
+ // different of the size of the number of children of the mlContainer if a duplicate locale is found.
+ int transSize = multilingualContentService.getTranslations(mlContainer).size();
+ int childSize = nodeService.getChildAssocs(mlContainer, ContentModel.ASSOC_MULTILINGUAL_CHILD, RegexQNamePattern.MATCH_ALL).size();
+
+ // if duplicate locale found
+ if(transSize != childSize)
+ {
+ // throw an exception and the current transaction will be rolled back. The properties will not be
+ // longer in an illegal state.
+ throw new IllegalArgumentException("The locale " + localeAfter +
+ " can't be changed for the node " + nodeRef +
+ " because this locale is already in use in an other translation of the same " +
+ ContentModel.TYPE_MULTILINGUAL_CONTAINER + ".");
+ }
+
+ // get the locale of ML Container
+ Locale localMlContainer = (Locale) nodeService.getProperty(
+ mlContainer,
+ ContentModel.PROP_LOCALE);
+
+ // if locale of the container is equals to the locale of
+ // the node (before update). The nodeRef is the pivot language
+ // and the locale of the mlContainer must be modified
+ if(localeBefore.equals(localMlContainer))
+ {
+ nodeService.setProperty(
+ mlContainer,
+ ContentModel.PROP_LOCALE,
+ localeAfter);
+ }
+
+ }
+
+ // else no action to perform
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualTestSuite.java b/source/java/org/alfresco/repo/model/ml/MultilingualTestSuite.java
new file mode 100644
index 0000000000..bd9d58ed8e
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualTestSuite.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.alfresco.repo.model.filefolder.FolderTypeTest;
+import org.alfresco.repo.model.ml.tools.ContentFilterLanguagesMapTest;
+import org.alfresco.repo.model.ml.tools.EmptyTranslationAspectTest;
+import org.alfresco.repo.model.ml.tools.MLContainerTypeTest;
+import org.alfresco.repo.model.ml.tools.MultilingualContentServiceImplTest;
+import org.alfresco.repo.model.ml.tools.MultilingualDocumentAspectTest;
+
+
+/**
+ * Multilingual test suite
+ *
+ * @author yanipig
+ */
+public class MultilingualTestSuite extends TestSuite
+{
+ /**
+ * Creates the test suite
+ *
+ * @return the test suite
+ */
+ public static Test suite()
+ {
+ TestSuite suite = new TestSuite();
+
+ suite.addTestSuite(EmptyTranslationAspectTest.class);
+ suite.addTestSuite(ContentFilterLanguagesMapTest.class);
+ suite.addTestSuite(MultilingualContentServiceImplTest.class);
+ suite.addTestSuite(MultilingualDocumentAspectTest.class);
+ suite.addTestSuite(MLContainerTypeTest.class);
+ suite.addTestSuite(FolderTypeTest.class);
+
+ return suite;
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/AbstractMultilingualTestCases.java b/source/java/org/alfresco/repo/model/ml/tools/AbstractMultilingualTestCases.java
new file mode 100644
index 0000000000..efda4f070c
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/AbstractMultilingualTestCases.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.node.archive.NodeArchiveService;
+import org.alfresco.repo.security.authentication.AuthenticationComponent;
+import org.alfresco.repo.transaction.TransactionUtil;
+import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.ml.ContentFilterLanguagesService;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.version.VersionService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Base multilingual test cases
+ *
+ * @author yanipig
+ */
+public abstract class AbstractMultilingualTestCases extends TestCase
+{
+
+ protected static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+
+ protected ServiceRegistry serviceRegistry;
+ protected AuthenticationComponent authenticationComponent;
+ protected TransactionService transactionService;
+ protected NodeService nodeService;
+ protected FileFolderService fileFolderService;
+ protected VersionService versionService;
+ protected MultilingualContentService multilingualContentService;
+ protected NodeRef folderNodeRef;
+ protected ContentFilterLanguagesService contentFilterLanguagesService;
+ protected NodeArchiveService nodeArchiveService;
+ @Override
+ protected void setUp() throws Exception
+ {
+ nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService");
+ serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ authenticationComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent");
+ transactionService = serviceRegistry.getTransactionService();
+ nodeService = serviceRegistry.getNodeService();
+ fileFolderService = serviceRegistry.getFileFolderService();
+ versionService = serviceRegistry.getVersionService();
+ multilingualContentService = (MultilingualContentService) ctx.getBean("MultilingualContentService");
+ contentFilterLanguagesService = (ContentFilterLanguagesService) ctx.getBean("ContentFilterLanguagesService");
+
+ // Run as admin
+ authenticationComponent.setCurrentUser("admin");
+
+ // Create a folder to work in
+ TransactionWork createFolderWork = new TransactionWork()
+ {
+ public NodeRef doWork() throws Exception
+ {
+ StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
+ NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
+ // Create the folder
+ NodeRef folderNodeRef = nodeService.createNode(
+ rootNodeRef,
+ ContentModel.ASSOC_CHILDREN,
+ QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"),
+ ContentModel.TYPE_FOLDER).getChildRef();
+ // done
+ return folderNodeRef;
+ }
+ };
+ folderNodeRef = TransactionUtil.executeInUserTransaction(transactionService, createFolderWork);
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ // Clear authentication
+ try
+ {
+ authenticationComponent.clearCurrentSecurityContext();
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ protected NodeRef createContent()
+ {
+ NodeRef contentNodeRef = fileFolderService.create(
+ folderNodeRef,
+ "" + System.currentTimeMillis(),
+ ContentModel.TYPE_CONTENT).getNodeRef();
+ // add some content
+ ContentWriter contentWriter = fileFolderService.getWriter(contentNodeRef);
+ contentWriter.putContent("ABC");
+ // done
+ return contentNodeRef;
+ }
+
+ public void testSetup() throws Exception
+ {
+ // Ensure that content can be created
+ createContent();
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/ContentFilterLanguagesMapTest.java b/source/java/org/alfresco/repo/model/ml/tools/ContentFilterLanguagesMapTest.java
new file mode 100644
index 0000000000..1e576df048
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/ContentFilterLanguagesMapTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Content filter language service test cases
+ *
+ * @see org.alfresco.service.cmr.ml.ContentFilterLanguagesService
+ * @see org.alfresco.repo.model.ml.ContentFilterLanguagesMap
+ *
+ * @author yanipig
+ */
+public class ContentFilterLanguagesMapTest extends AbstractMultilingualTestCases
+{
+
+ public void testGetFilterLanguages() throws Exception
+ {
+ // get the list of content filter languages
+ List lggs = contentFilterLanguagesService.getFilterLanguages();
+
+ // Ensure that the list is not null
+ assertNotNull("Language list is null", lggs);
+
+ // Ensure that the list is read-only
+ try
+ {
+ lggs.add("NEW LOCALE");
+ assertTrue("Add a value to the content filter language list is not permit, this list would be read only", false);
+ }
+ catch (Exception e)
+ {
+ // test case ok
+ }
+
+ try
+ {
+ lggs.remove(0);
+ assertTrue("Remove a value to the content filter language list is not permit, this list would be read only", false);
+ }
+ catch (Exception e)
+ {
+ // test case ok
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testGetMissingLanguages() throws Exception
+ {
+ List lggs = contentFilterLanguagesService.getFilterLanguages();
+
+ // get missing languages with null parameter
+ List missingLggsNull = contentFilterLanguagesService.getMissingLanguages(null);
+
+ // Ensure that the returned list is not null
+ assertNotNull("Language list returned with the null parameter is null", missingLggsNull);
+ // Ensure that the returned list is entire
+ assertEquals("Language list returned with the null parameter corrupted", missingLggsNull.size(), lggs.size());
+
+ // get missing languages with empty collection parameter
+ List missingLggsEmpty = contentFilterLanguagesService.getMissingLanguages(Collections.EMPTY_LIST);
+
+ // Ensure that the returned list is not null
+ assertNotNull("Language list returned with the empty parameter is null", missingLggsEmpty);
+ // Ensure that the returned list is entire
+ assertEquals("Language list returned with the empty parameter corrupted", missingLggsEmpty.size(), lggs.size());
+
+ // get missing languages with a two locale list
+ List param = new ArrayList();
+ param.add(0, lggs.get(0));
+ param.add(1, lggs.get(1));
+ List missingLggsOk = contentFilterLanguagesService.getMissingLanguages(param);
+
+ // Ensure that the returned list is not null
+ assertNotNull("Language list returned with the correct parameter is null", missingLggsOk);
+ // Ensure that the returned list size is correct
+ assertEquals("Language list size returned with the correct parameter is not correct", missingLggsOk.size(), (lggs.size() - 2));
+ // Ensure that the returned list don't content the preceding locales
+ assertFalse("Language found : " + param.get(0), missingLggsOk.contains(param.get(0)));
+ assertFalse("Language found : " + param.get(1), missingLggsOk.contains(param.get(1)));
+ // get missing languages with a not found locale
+ param.add(2, "WRONG LOCALE CODE");
+ List missingLggsWrong = contentFilterLanguagesService.getMissingLanguages(param);
+
+ // Ensure that the returned list is not null
+ assertNotNull("Language list returned with the wrong parameter is null", missingLggsWrong);
+ // Ensure that the returned list size is correct
+ assertEquals("Language list size returned with the correct parameter is not correct", missingLggsWrong.size(), (lggs.size() - 2));
+ // Ensure that the returned list don't content the wrong locale
+ assertFalse("Language found : " + param.get(0), missingLggsWrong.contains(param.get(0)));
+ assertFalse("Language found : " + param.get(1), missingLggsWrong.contains(param.get(1)));
+ assertFalse("Language found : " + param.get(2), missingLggsWrong.contains(param.get(2)));
+ }
+
+ public void testISOCodeConvertions() throws Exception
+ {
+ // New ISO code list
+ String[] newCode = {"he", "id", "yi"};
+ String[] oldCode = {"iw", "in", "ji"};
+
+ Locale loc0 = new Locale(newCode[0]);
+ Locale loc1 = new Locale(newCode[1]);
+ Locale loc2 = new Locale(newCode[2]);
+
+ // Ensure that java.util.Locale has converted the new ISO code into new iso code
+ assertEquals("java.util.Locale Convertion not correct for " + newCode[0], oldCode[0], loc0.getLanguage());
+ assertEquals("java.util.Locale Convertion not correct for " + newCode[1], oldCode[1], loc1.getLanguage());
+ assertEquals("java.util.Locale Convertion not correct for " + newCode[2], oldCode[2], loc2.getLanguage());
+
+ // Ensure that the convertion is correcte
+ assertEquals("Convertion of new ISO codes not correct for " + newCode[0], oldCode[0], contentFilterLanguagesService.convertToOldISOCode(newCode[0]));
+ assertEquals("Convertion of new ISO codes not correct for " + newCode[1], oldCode[1], contentFilterLanguagesService.convertToOldISOCode(newCode[1]));
+ assertEquals("Convertion of new ISO codes not correct for " + newCode[2], oldCode[2], contentFilterLanguagesService.convertToOldISOCode(newCode[2]));
+
+
+ assertEquals("Convertion of old ISO codes not correct for " + oldCode[0], newCode[0], contentFilterLanguagesService.convertToNewISOCode(oldCode[0]));
+ assertEquals("Convertion of old ISO codes not correct for " + oldCode[1], newCode[1], contentFilterLanguagesService.convertToNewISOCode(oldCode[1]));
+ assertEquals("Convertion of old ISO codes not correct for " + oldCode[2], newCode[2], contentFilterLanguagesService.convertToNewISOCode(oldCode[2]));
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java b/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java
new file mode 100644
index 0000000000..af2f8f214e
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Empty translations aspect test cases
+ *
+ * @see org.alfresco.service.cmr.ml.EmptyTranslationAspect
+ *
+ * @author yanipig
+ */
+public class EmptyTranslationAspectTest extends AbstractMultilingualTestCases {
+
+ protected ContentService contentService;
+
+ protected void setUp() throws Exception
+ {
+ contentService = (ContentService) ctx.getBean("ContentService");
+ super.setUp();
+ }
+
+ public void testCopy() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef empty = null;
+
+ multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+
+ boolean exceptionCatched = false;
+ NodeRef copy = null;
+
+ try
+ {
+ copy = fileFolderService.copy(
+ empty,
+ nodeService.getPrimaryParent(empty).getParentRef(),
+ "copyOfEmpty" + System.currentTimeMillis()).getNodeRef();
+
+ // test failed
+ }
+ catch (Exception ignore)
+ {
+ exceptionCatched = true;
+ }
+
+ // Ensure that the copy of an empty translation throws an exception
+ assertTrue("The copy of a translation must throws an exception", exceptionCatched);
+ // Ensure that the copy node is null
+ assertNull("The copy must fail ", copy);
+ }
+
+ public void testDeleteNode() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef empty = null;
+
+ multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+
+ nodeService.deleteNode(empty);
+
+ // Ensure that the empty translation is removed from the workspace
+ assertFalse("The empty translation must be removed from the wokspace", nodeService.exists(empty));
+ // Ensure that the empty translation is not archived
+ assertFalse("The empty translation must be removed from the wokspace", nodeService.exists(nodeArchiveService.getArchivedNode(empty)));
+ }
+
+ public void testGetContent() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef otherTranslation = createContent();
+
+ NodeRef empty = null;
+
+ NodeRef mlContainer = multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+ multilingualContentService.addTranslation(otherTranslation, pivot, Locale.KOREAN);
+
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+
+ // Modify the content of the pivot
+ String contentString = fileFolderService.getReader(pivot).getContentString();
+ contentString += "_TEST_";
+
+ fileFolderService.getWriter(pivot).putContent(contentString);
+
+ // Ensure that the URL property of the empty translation content is null
+ assertNull("The URL property of an empty translation must be null", ((ContentData) nodeService.getProperty(empty, ContentModel.PROP_CONTENT)).getContentUrl());
+ // Ensure that the content retourned by a get reader of the empty document is the same as the pivot
+ assertEquals("The content retourned of the empty translation must be the same that the content of the pivot",
+ fileFolderService.getReader(pivot).getContentString(),
+ fileFolderService.getReader(empty).getContentString());
+ // Ensure that the content retourned by a get reader of the empty document is different to the non-pivot translation
+ assertNotSame("The content retourned of the empty translation must be different that the content of a non-pivot translation",
+ fileFolderService.getReader(otherTranslation).getContentString(),
+ fileFolderService.getReader(empty).getContentString());
+
+
+ // modify the pivot
+ Map props = nodeService.getProperties(mlContainer);
+ props.put(ContentModel.PROP_LOCALE, Locale.KOREAN);
+ nodeService.setProperties(mlContainer, props);
+
+ // Ensure that the modicfication of the pivot is take in account
+ assertEquals("The modification of the pivot is not take in account",
+ fileFolderService.getReader(otherTranslation).getContentString(),
+ fileFolderService.getReader(empty).getContentString());
+ }
+
+ public void testUpdateContent() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef empty = null;
+
+ multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+
+ // update the empty translation content
+ ContentWriter writer = contentService.getWriter(empty, ContentModel.PROP_CONTENT, true);
+ writer.setMimetype("text/plain");
+ writer.putContent("ANY_CONTENT");
+
+ // Ensure that the URL property of the empty translation content is not null
+ assertNotNull("The url of an updated (ex)empty transation can't be null", ((ContentData) nodeService.getProperty(empty, ContentModel.PROP_CONTENT)).getContentUrl());
+ // Ensure that the mlEmptyTranslation aspect is removed
+ assertFalse("The " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " aspect of an updated (ex)empty translation must be removed", nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION));
+ // Ensure that the mlDocument aspect is not removed
+ assertTrue("The " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " aspect of an updated (ex)empty translation must be keeped", nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ // Ensure that the content is written
+ assertEquals("The content of the (ex)empty translation is not correct",
+ fileFolderService.getReader(empty).getContentString(),
+ "ANY_CONTENT");
+ // Ensure that the content is different that the content of the pivot
+ assertNotSame("The content of the (ex)empty translation is not updated and is the same as the content of the pivot",
+ fileFolderService.getReader(empty).getContentString(),
+ fileFolderService.getReader(pivot).getContentString());
+ }
+
+ public void testRemoveAspect() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef empty = null;
+
+ multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+
+ // 1. remove mlEmptyTranslation aspect with empty content
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+ nodeService.removeAspect(empty, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
+
+ // Ensure that the empty translation is deleted
+ assertFalse("After removed the " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " of an empty translation with no content the node must be deleted",
+ nodeService.exists(empty));
+ assertFalse("After removed the " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " of an empty translation with no content the node must be deleted and can't be archived",
+ nodeService.exists(nodeArchiveService.getArchivedNode(empty)));
+
+ // 2. remove mlDocument aspect with empty content
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+ nodeService.removeAspect(empty, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
+
+ // Ensure that the empty translation is deleted
+ assertFalse("After removed the " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " of an empty translation with no content the node must be deleted",
+ nodeService.exists(empty));
+ assertFalse("After removed the " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " of an empty translation with no content the node must be deleted and can't be archived",
+ nodeService.exists(nodeArchiveService.getArchivedNode(empty)));
+
+ // 3. remove mlEmptyTranslation aspect with not empty content
+ empty = multilingualContentService.addEmptyTranslation(pivot, "empty_" + System.currentTimeMillis(), Locale.CHINESE);
+ ContentWriter writer = contentService.getWriter(empty, ContentModel.PROP_CONTENT, true);
+ writer.setMimetype("text/plain");
+ writer.putContent("ANY_CONTENT");
+ nodeService.removeAspect(empty, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
+
+ // Ensure that the empty translation is NOT deleted
+ assertTrue("After removed the " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " of an empty translation with content the node must NOT be deleted",
+ nodeService.exists(empty));
+ // Ensure that the mlEmptyTranslation aspect is removed
+ assertFalse("After removed the " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " of an empty translation with content this aspect must be removed",
+ nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION));
+ // Ensure that the mlEmptyDocument in NOT removed
+ assertTrue("After removed the " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " of an empty translation with content the " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " aspect must be keeped",
+ nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java b/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java
new file mode 100644
index 0000000000..7e2b1fa13c
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Multilingual container type test cases
+ *
+ * @see org.alfresco.service.cmr.ml.MLContainerType
+ *
+ * @author yanipig
+ */
+public class MLContainerTypeTest extends AbstractMultilingualTestCases {
+
+
+ public void testDeleteNode() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef empty;
+
+ NodeRef mlContainer = multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+ empty = multilingualContentService.addEmptyTranslation(pivot, "Empty_" + System.currentTimeMillis(), Locale.ENGLISH);
+
+ nodeService.deleteNode(mlContainer);
+
+ // Ensure that the mlContainer is deleted
+ assertFalse("The mlContainer must be deleted", nodeService.exists(mlContainer));
+ // Ensure that the empty translation is deleted
+ assertFalse("The mlEmptyTranslation must be deleted", nodeService.exists(empty));
+ // Ensure that the non-empty document is not deleted
+ assertTrue("The mlDocument must not be deleted", nodeService.exists(pivot));
+ // Ensure that the mlDocument property of the non-empty document is removed
+ assertFalse("The " + ContentModel.ASPECT_MULTILINGUAL_DOCUMENT + " aspect of the mlDocument must be removed",
+ nodeService.hasAspect(pivot, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ }
+
+ @SuppressWarnings("unused")
+ public void testEditLocale() throws Exception
+ {
+ NodeRef trans1 = createContent();
+ NodeRef trans2 = createContent();
+ NodeRef trans3 = createContent();
+ NodeRef empty = null;
+
+ NodeRef mlContainer = multilingualContentService.makeTranslation(trans1, Locale.FRENCH);
+ multilingualContentService.addTranslation(trans2, trans1, Locale.GERMAN);
+ multilingualContentService.addTranslation(trans3, trans1, Locale.ITALIAN);
+ empty = multilingualContentService.addEmptyTranslation(trans1, "EMPTY_" + System.currentTimeMillis(), Locale.JAPANESE);
+
+ // 1. Locale as null
+
+ // Ensure that the setting of the locale of the mlContainer as null throws an excpetion
+ assertTrue("The setting of the locale of a mlContainer must throws an exception",
+ setLocaleProp(mlContainer, null));
+ // Ensure that the locale of the mlContainer is not changed
+ assertEquals("The locale of the mlContainer would not be changed",
+ Locale.FRENCH,
+ nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+
+ // 2. Set an non-existing locale
+
+ // Ensure that the setting of the locale of the mlContainer as a non-existing translation language throws an excpetion
+ assertTrue("The setting of the locale of a mlContainer as a non-existing translation language must throws an exception",
+ setLocaleProp(mlContainer, Locale.US));
+ // Ensure that the locale of the mlContainer is not changed
+ assertEquals("The locale of the mlContainer would not be changed",
+ Locale.FRENCH,
+ nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+
+ // 3. Set the locale of a empty translation
+
+ // Ensure that the setting of the locale of the mlContainer as an empty translation language throws an excpetion
+ assertTrue("The setting of the locale of a mlContainer as an empty translation language must throws an exception",
+ setLocaleProp(mlContainer, Locale.JAPANESE));
+ // Ensure that the locale of the mlContainer is not changed
+ assertEquals("The locale of the mlContainer would not be changed",
+ Locale.FRENCH,
+ nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+
+ // 4. Set an existing and valid locale
+
+ // Ensure that the setting of the locale of the mlContainer as an existing and a non-empty translation DOESN'T throw an excpetion
+ assertFalse("The setting of the locale of a mlContainer as an existing and a non-empty translation DOESN'T throw an excpetion",
+ setLocaleProp(mlContainer, Locale.ITALIAN));
+ // Ensure that the locale of the mlContainer is not changed
+ assertEquals("The locale of the mlContainer would be changed",
+ Locale.ITALIAN,
+ nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+
+ }
+
+
+
+
+ private boolean setLocaleProp(NodeRef node, Locale locale) throws Exception
+ {
+ Map props = nodeService.getProperties(node);
+ props.put(ContentModel.PROP_LOCALE, locale);
+
+ boolean exceptionCatched = false;
+
+ try
+ {
+ nodeService.setProperties(node, props);
+
+ }
+ catch (IllegalArgumentException ignore)
+ {
+ exceptionCatched = true;
+ }
+ catch(Exception ex)
+ {
+ throw new Exception(ex);
+ }
+
+
+ return exceptionCatched;
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java
new file mode 100644
index 0000000000..68153ee3ec
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+
+import org.alfresco.i18n.I18NUtil;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.version.Version;
+import org.alfresco.service.cmr.version.VersionHistory;
+
+/**
+ * @see org.alfresco.repo.ml.MultilingualContentServiceImpl
+ *
+ * @author Derek Hulley
+ * @author Philippe Dubois
+ */
+public class MultilingualContentServiceImplTest extends AbstractMultilingualTestCases
+{
+
+ public void testMakeTranslation() throws Exception
+ {
+ NodeRef contentNodeRef = createContent();
+ // Turn the content into a translation with the appropriate structures
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(contentNodeRef, Locale.CHINESE);
+ // Check it
+ assertNotNull("Container not created", mlContainerNodeRef);
+ // Check the container child count
+ assertEquals("Incorrect number of child nodes", 1, nodeService.getChildAssocs(mlContainerNodeRef).size());
+ }
+
+ public void testAddTranslationUsingContainer() throws Exception
+ {
+ // Make a container with a single translation
+ NodeRef chineseContentNodeRef = createContent();
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(chineseContentNodeRef, Locale.CHINESE);
+ // Create some more content
+ NodeRef frenchContentNodeRef = createContent();
+ // Make this a translation of the Chinese
+ NodeRef newMLContainerNodeRef = multilingualContentService.addTranslation(
+ frenchContentNodeRef,
+ mlContainerNodeRef,
+ Locale.FRENCH);
+ // Make sure that the original container was used
+ assertEquals("Existing container should have been used", mlContainerNodeRef, newMLContainerNodeRef);
+ // Check the container child count
+ assertEquals("Incorrect number of child nodes", 2, nodeService.getChildAssocs(mlContainerNodeRef).size());
+ }
+
+ public void testAddTranslationUsingContent() throws Exception
+ {
+ // Make a container with a single translation
+ NodeRef chineseContentNodeRef = createContent();
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(chineseContentNodeRef, Locale.CHINESE);
+ // Create some more content
+ NodeRef frenchContentNodeRef = createContent();
+ // Make this a translation of the Chinese
+ NodeRef newMLContainerNodeRef = multilingualContentService.addTranslation(
+ frenchContentNodeRef,
+ chineseContentNodeRef,
+ Locale.FRENCH);
+ // Make sure that the original container was used
+ assertEquals("Existing container should have been used", mlContainerNodeRef, newMLContainerNodeRef);
+ // Check the container child count
+ assertEquals("Incorrect number of child nodes", 2, nodeService.getChildAssocs(mlContainerNodeRef).size());
+ }
+
+ @SuppressWarnings("unused")
+ public void testGetMissingTranslation() throws Exception
+ {
+ List langList = contentFilterLanguagesService.getFilterLanguages();
+ int langListSize = langList.size();
+
+ // make sure that it exists at least tree language filter
+ assertFalse("The testGetMissingTranslation test case needs at least three language", langListSize < 3);
+
+ // get the first tree locale of the content filter language list
+ Locale loc1 = I18NUtil.parseLocale(langList.get(0));
+ Locale loc2 = I18NUtil.parseLocale(langList.get(1));
+ Locale loc3 = I18NUtil.parseLocale(langList.get(2));
+
+ // create three content
+ NodeRef nodeRef1 = createContent();
+ NodeRef nodeRef2 = createContent();
+ NodeRef nodeRef3 = createContent();
+
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(nodeRef1, loc1);
+
+ List missing = multilingualContentService.getMissingTranslations(mlContainerNodeRef, false);
+
+ // make sure that the missing language list size is correct
+ assertFalse("Missing Translation Size false. " +
+ "Real size : " + missing.size() + ". Normal Size " + (langListSize - 1), missing.size() != (langListSize - 1));
+
+ // make sure that the missing language list is correct
+ assertFalse("Missing Translation List false. Locale " + loc1 + " found", missing.contains(loc1.toString()));
+
+ multilingualContentService.addTranslation(nodeRef2, mlContainerNodeRef, loc2);
+ multilingualContentService.addTranslation(nodeRef3, mlContainerNodeRef, loc3);
+
+
+ missing = multilingualContentService.getMissingTranslations(mlContainerNodeRef, false);
+
+ // make sure that the missing language list size is correct
+ assertFalse("Missing Translation Size false. " +
+ "Real size : " + missing.size() + ". Normal Size " + (langListSize - 3), missing.size() != (langListSize - 3));
+
+ // make sure that the missing language list is correct
+ assertFalse("Missing Translation List false. Locale " + loc2 + " or " + loc3 + " found", missing.contains(loc2.toString()) || missing.contains(loc3.toString()));
+ }
+
+ @SuppressWarnings("unused")
+ public void testPivotTranslation() throws Exception
+ {
+ NodeRef chineseContentNodeRef = createContent();
+
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(chineseContentNodeRef, Locale.CHINESE);
+
+ // make sure that the pivot language is set
+ assertNotNull("Pivot language not set", nodeService.getProperty(mlContainerNodeRef, ContentModel.PROP_LOCALE));
+
+ // make sure that the pivot language is correctly set
+ assertTrue("Pivot language not correctly set", nodeService.getProperty(mlContainerNodeRef, ContentModel.PROP_LOCALE).equals(Locale.CHINESE));
+
+ NodeRef frenchContentNodeRef = createContent();
+ multilingualContentService.addTranslation(frenchContentNodeRef, chineseContentNodeRef, Locale.FRENCH);
+
+ // make sure that the pivot noderef is correct
+ assertTrue("Pivot node ref not correct", multilingualContentService.getPivotTranslation(mlContainerNodeRef).equals(chineseContentNodeRef));
+
+ // modify the pivot language
+ nodeService.setProperty(mlContainerNodeRef, ContentModel.PROP_LOCALE, Locale.FRENCH);
+
+ // make sure that the modified pivot noderef is correct
+ assertTrue("Pivot node ref not correct", multilingualContentService.getPivotTranslation(mlContainerNodeRef).equals(frenchContentNodeRef));
+
+ }
+
+ @SuppressWarnings("unused")
+ public void testCreateEmptyTranslation() throws Exception
+ {
+ NodeRef chineseContentNodeRef = createContent();
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(chineseContentNodeRef, Locale.CHINESE);
+
+ NodeRef empty = multilingualContentService.addEmptyTranslation(chineseContentNodeRef, "" + System.currentTimeMillis(), Locale.CANADA);
+
+ // Ensure that the empty translation is not null
+ assertNotNull("The creation of the empty document failed ", empty);
+ // Ensure that the empty translation has the mlDocument aspect
+ assertTrue("The empty document must have the mlDocument aspect", nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ // Ensure that the empty translation has the mlEmptyTranslation aspect
+ assertTrue("The empty document must have the mlEmptyTranslation aspect", nodeService.hasAspect(empty, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION));
+ }
+
+ @SuppressWarnings("unused")
+ public void testCreateEdition() throws Exception
+ {
+ // Make some content
+ NodeRef chineseContentNodeRef = createContent();
+ NodeRef frenchContentNodeRef = createContent();
+ NodeRef japaneseContentNodeRef = createContent();
+ // Add to container
+ NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(chineseContentNodeRef, Locale.CHINESE);
+ multilingualContentService.addTranslation(frenchContentNodeRef, mlContainerNodeRef, Locale.FRENCH);
+ multilingualContentService.addTranslation(japaneseContentNodeRef, mlContainerNodeRef, Locale.JAPANESE);
+ // Check the container child count
+ assertEquals("Incorrect number of child nodes", 3, nodeService.getChildAssocs(mlContainerNodeRef).size());
+
+ // Version each of the documents
+ List nodeRefs = new ArrayList(3);
+ nodeRefs.add(chineseContentNodeRef);
+ nodeRefs.add(frenchContentNodeRef);
+ nodeRefs.add(japaneseContentNodeRef);
+ versionService.createVersion(nodeRefs, null);
+ // Get the current versions of each of the documents
+ Version chineseVersionPreEdition = versionService.getCurrentVersion(chineseContentNodeRef);
+ Version frenchVersionPreEdition = versionService.getCurrentVersion(frenchContentNodeRef);
+ Version japaneseVersionPreEdition = versionService.getCurrentVersion(japaneseContentNodeRef);
+
+ // Create the edition, keeping the Chinese translation as the basis
+ multilingualContentService.createEdition(chineseContentNodeRef);
+ // Check the container child count
+ assertEquals("Incorrect number of child nodes", 1, nodeService.getChildAssocs(mlContainerNodeRef).size());
+
+ // Get the document versions now
+ Version chineseVersionPostEdition = versionService.getCurrentVersion(chineseContentNodeRef);
+ assertFalse("Expected document to be gone", nodeService.exists(frenchContentNodeRef));
+ assertFalse("Expected document to be gone", nodeService.exists(japaneseContentNodeRef));
+
+ // Now be sure that we can get the required information using the version service
+ VersionHistory mlContainerVersionHistory = versionService.getVersionHistory(mlContainerNodeRef);
+ Collection mlContainerVersions = mlContainerVersionHistory.getAllVersions();
+ // Loop through and get all the children of each version
+ for (Version mlContainerVersion : mlContainerVersions)
+ {
+ NodeRef versionedMLContainerNodeRef = mlContainerVersion.getFrozenStateNodeRef();
+ // Get all the children
+ Map translationsByLocale = multilingualContentService.getTranslations(
+ versionedMLContainerNodeRef);
+ // Count the children
+ int count = translationsByLocale.size();
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java b/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java
new file mode 100644
index 0000000000..d7b4a70148
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.model.ml.tools;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Multilingual document aspect test cases
+ *
+ * @see org.alfresco.service.cmr.ml.MultilingualDocumentAspect
+ *
+ * @author yanipig
+ */
+public class MultilingualDocumentAspectTest extends AbstractMultilingualTestCases
+{
+ public void testCopy() throws Exception
+ {
+ NodeRef original = createContent();
+ NodeRef mlContainer = multilingualContentService.makeTranslation(original, Locale.FRENCH);
+
+ NodeRef copy =
+ fileFolderService.copy(original, nodeService.getPrimaryParent(original).getParentRef(), "COPY" + System.currentTimeMillis()).getNodeRef();
+
+ // Ensure that the copy removes the mlDocument aspect
+ assertFalse("The copy of a mlDocument can't have the multilingual aspect", nodeService.hasAspect(copy, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+
+ // Ensure that the copy removes the association between the mlConatiner and the new node
+ assertEquals("The copy of a mlDocument can't be a children of the mlContainer", 1, multilingualContentService.getTranslations(mlContainer).size());
+
+ // Ensure that the copy removes the Locale property of the new node
+ assertNull("The copy of a mlDocument can't keep the locale property", nodeService.getProperty(copy, ContentModel.PROP_LOCALE));
+ }
+
+ public void testDeleteNode() throws Exception
+ {
+ NodeRef trad1 = createContent();
+ NodeRef trad2 = createContent();
+ NodeRef trad3 = createContent();
+
+ NodeRef parent = nodeService.getPrimaryParent(trad1).getParentRef();
+
+ NodeRef mlContainer = multilingualContentService.makeTranslation(trad1, Locale.FRENCH);
+ multilingualContentService.addTranslation(trad2, trad1, Locale.GERMAN);
+ multilingualContentService.addTranslation(trad3, trad1, Locale.ITALIAN);
+
+ nodeService.deleteNode(trad3);
+
+ // Ensure that the deleted node is romoved from its space
+ assertEquals("The deleted node must be removed to the space", 2, nodeService.getChildAssocs(parent).size());
+ // Ensure that the mlContainer doesn't keep an association to the deleted node
+ assertEquals("The deleted node must be removed to the child associations of the mlContainer", 2, multilingualContentService.getTranslations(mlContainer).size());
+
+ // retore the deleted node
+ NodeRef restoredNode = nodeArchiveService.restoreArchivedNode(nodeArchiveService.getArchivedNode(trad3)).getRestoredNodeRef();
+
+ // Ensure that the restored node is restored to it s original space
+ assertEquals("The restaured node must be restaured to the the space", 3, nodeService.getChildAssocs(parent).size());
+ // Ensure that the restored node is not linked to the mlContainer
+ assertEquals("The restaured node would not be restaured to the mlContainer", 2, multilingualContentService.getTranslations(mlContainer).size());
+ // Ensure that the restored node doesn't keep the mlDocument aspect
+ assertFalse("The restaured node can't keep the multilingual aspect", nodeService.hasAspect(restoredNode, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ // Ensure that the restored node doesn't keep the locale property
+ assertNull("The restaured node can't keep the locale property", nodeService.getProperty(restoredNode, ContentModel.PROP_LOCALE));
+
+
+ }
+
+ public void testDeletePivot() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef trans1 = createContent();
+ NodeRef mlContainer = multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+ multilingualContentService.addTranslation(trans1, pivot, Locale.KOREAN);
+
+ //nodeService.deleteNode(trans1);
+ nodeService.deleteNode(pivot);
+
+ // Ensure that pivot is removed
+ assertFalse("The pivot would be removed", nodeService.exists(pivot));
+ // Ensure that the mlContainer is removed
+ assertFalse("The mlContainer must be removed if the pivot is removed", nodeService.exists(mlContainer));
+ // Ensure that trans1 is NOT removed
+ assertTrue("The last translation would not be removed", nodeService.exists(trans1));
+ // Ensure that trans1 has no mlDocument aspect
+ assertFalse("The last translation can't keep the multilingual aspect", nodeService.hasAspect(trans1, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT));
+ // Ensure that trans1 has no locale propety
+ assertNull("The last translation can't keep the locale property", nodeService.getProperty(trans1, ContentModel.PROP_LOCALE));
+ }
+
+ public void testDeleteLastNode() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef mlContainer = multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+
+ nodeService.deleteNode(pivot);
+
+ // Ensure that the mlContainer is removed too
+ assertFalse("The mlContainer must be removed if the last translation is removed", nodeService.exists(mlContainer));
+
+ }
+
+ public void testRemoveAspect() throws Exception
+ {
+ // entierly covered by the delete tests
+ }
+
+ public void testUpdateLocale() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef trans1 = createContent();
+ NodeRef mlContainer = multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+ multilingualContentService.addTranslation(trans1, pivot, Locale.KOREAN);
+
+ // modify the locale for the translation
+ Map props = nodeService.getProperties(trans1);
+ props.put(ContentModel.PROP_LOCALE, Locale.GERMAN);
+ nodeService.setProperties(trans1, props);
+
+ // Ensure that the pivot reference is not changed for the mlContainer and the locale is changed for the translation
+ assertEquals("The locale for the pivot would be changed ",Locale.GERMAN, nodeService.getProperty(trans1, ContentModel.PROP_LOCALE));
+ assertEquals("The pivot reference would not be changed in the mlContainer", Locale.FRENCH, nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+
+ // modify the locale for the pivot
+ props = nodeService.getProperties(pivot);
+ props.put(ContentModel.PROP_LOCALE, Locale.US);
+ nodeService.setProperties(pivot, props);
+
+ // Ensure that the pivot reference is changed (in the pivot and in the mlContainer)
+ assertEquals("The locale for the pivot would be changed ", Locale.US, nodeService.getProperty(pivot, ContentModel.PROP_LOCALE));
+ assertEquals("The pivot reference would be changes in the mlContainer", Locale.US, nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE));
+ }
+
+ public void testUpdateRedundantLocale() throws Exception
+ {
+ NodeRef pivot = createContent();
+ NodeRef trans1 = createContent();
+ NodeRef trans2 = createContent();
+
+ multilingualContentService.makeTranslation(pivot, Locale.FRENCH);
+ multilingualContentService.addTranslation(trans1, pivot, Locale.KOREAN);
+ multilingualContentService.addTranslation(trans2, pivot, Locale.JAPANESE);
+
+ // 1. Try with redundant locale
+
+ // modify the locale for the translation 2
+ Map props = nodeService.getProperties(trans2);
+ props.put(ContentModel.PROP_LOCALE, Locale.KOREAN);
+
+ boolean exceptionCatched = false;
+
+ try
+ {
+ nodeService.setProperties(trans2, props);
+ // test failed
+ } catch (Exception ignore)
+ {
+ exceptionCatched = true;
+ }
+
+ // Ensure that the the exception was catched.
+ assertTrue("The modification of this locale must catch an exception because it is already in use in another translation", exceptionCatched);
+ // Ensure that the locale of the trans2 is unchanged
+ assertEquals("The locale must not be changed",
+ Locale.JAPANESE,
+ (Locale) nodeService.getProperty(trans2, ContentModel.PROP_LOCALE));
+
+ // 2. Try with a non-redundant locale
+
+ props = nodeService.getProperties(trans2);
+ props.put(ContentModel.PROP_LOCALE, Locale.ITALIAN);
+
+ exceptionCatched = false;
+
+ try
+ {
+ nodeService.setProperties(trans2, props);
+
+ } catch (Exception ignore)
+ {
+ // test failed
+ exceptionCatched = true;
+ }
+
+ // Ensure that the exception was not catched
+ assertFalse("The modification of the locale would not throws an exception", exceptionCatched);
+ // Ensure that the locale is modified
+ assertEquals("The locale must be changed",
+ Locale.ITALIAN,
+ (Locale) nodeService.getProperty(trans2, ContentModel.PROP_LOCALE));
+ }
+}
+
diff --git a/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java b/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java
new file mode 100644
index 0000000000..24974c84db
--- /dev/null
+++ b/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.node;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.alfresco.i18n.I18NUtil;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Interceptor to
+ * - filter the multilingual nodes to display the documents in the prefered language of teh user
+ *
+ * @author yanipig
+ */
+public class MLTranslationInterceptor implements MethodInterceptor
+{
+ private static Log logger = LogFactory
+ .getLog(MLTranslationInterceptor.class);
+
+ private NodeService nodeService;
+
+ private MultilingualContentService multilingualContentService;
+
+ @SuppressWarnings("unchecked")
+ public Object invoke(MethodInvocation invocation) throws Throwable
+ {
+ Object ret = null;
+ String methodName = invocation.getMethod().getName();
+
+ // intercept the methods getChildAssocs and getChildByNames to apply filter.
+ if (methodName.equals("getChildAssocs") || methodName.equals("getChildByName"))
+ {
+ ret = invocation.proceed();
+
+ NodeRef parent = (NodeRef) invocation.getArguments()[0];
+
+ // all the association returned by the method
+ List allChildAssoc = (List) ret;
+
+ // get the user content filter language
+ Locale filterLocale = I18NUtil.getContentLocaleOrNull();
+
+ if(filterLocale != null
+ && nodeService.getType(parent).equals(ContentModel.TYPE_FOLDER)
+ && ret != null
+ && !allChildAssoc.isEmpty()
+ )
+ {
+
+ // the list of Association to return
+ List toReturn = new ArrayList();
+ // the ml containers found in the folder
+ List mlContainers = new ArrayList();
+
+ // construct the list of ML Container
+ for (ChildAssociationRef assoc : allChildAssoc)
+ {
+ NodeRef child = assoc.getChildRef();
+
+ QName type = nodeService.getType(child);
+
+ if(type.equals(ContentModel.TYPE_CONTENT) &&
+ nodeService.hasAspect(child, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ NodeRef container = multilingualContentService.getTranslationContainer(child);
+
+ if (!mlContainers.contains(container))
+ {
+ mlContainers.add(container);
+ }
+ }
+ else
+ {
+ // no specific treatment for folder and non-multilingual document
+ toReturn.add(assoc);
+ }
+ }
+
+ // for each mlContainer found, choose the unique document to return
+ for(NodeRef container : mlContainers)
+ {
+ // get each translation language
+ Set locales = multilingualContentService.getTranslations(container).keySet();
+
+ if(locales != null && locales.size() > 0)
+ {
+ Locale matchedLocal = I18NUtil.getNearestLocale(filterLocale, locales);
+
+ NodeRef matchedTranslation = null;
+
+ // if the filter language is not found
+ if(matchedLocal == null)
+ {
+ // get the pivot translation
+ matchedTranslation = multilingualContentService.getPivotTranslation(container);
+ }
+ else
+ {
+ // get the matched translation
+ matchedTranslation = multilingualContentService.getTranslations(container).get(matchedLocal);
+ }
+
+ toReturn.add(new ChildAssociationRef(null, null, null, matchedTranslation));
+ }
+ }
+
+ ret = toReturn;
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Filter has found " +
+ allChildAssoc.size() + " entries, " +
+ mlContainers.size() + " different ML Container " +
+ "and returns " + toReturn.size() + " nodes");
+ }
+ }
+
+ } else
+ {
+ ret = invocation.proceed();
+ }
+
+ return ret;
+ }
+
+ public MultilingualContentService getMultilingualContentService()
+ {
+ return multilingualContentService;
+ }
+
+ public void setMultilingualContentService(
+ MultilingualContentService multilingualContentService)
+ {
+ this.multilingualContentService = multilingualContentService;
+ }
+
+ public NodeService getNodeService()
+ {
+ return nodeService;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java
index b3e15f84a4..c317446cba 100644
--- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java
+++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java
@@ -37,6 +37,7 @@ import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.ml.ContentFilterLanguagesService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.CopyService;
@@ -255,7 +256,7 @@ public class ServiceDescriptorRegistry
*/
public ActionService getActionService()
{
- return (ActionService)getService(ACTION_SERVICE);
+ return (ActionService)getService(ACTION_SERVICE);
}
/* (non-Javadoc)
@@ -364,6 +365,14 @@ public class ServiceDescriptorRegistry
return (AttributeService)getService(ATTRIBUTE_SERVICE);
}
+ /* (non-Javadoc)
+ * @see org.alfresco.service.ServiceRegistry#getContentFilterLanguagesService()
+ */
+ public ContentFilterLanguagesService getContentFilterLanguagesService()
+ {
+ return (ContentFilterLanguagesService) getService(CONTENT_FILTER_LANGUAGES_SERVICE);
+ }
+
/* (non-Javadoc)
* @see org.alfresco.service.ServiceRegistry#getAVMLockingService()
*/
diff --git a/source/java/org/alfresco/repo/version/VersionModel.java b/source/java/org/alfresco/repo/version/VersionModel.java
index f3ebcb7bd9..b28b74a38b 100644
--- a/source/java/org/alfresco/repo/version/VersionModel.java
+++ b/source/java/org/alfresco/repo/version/VersionModel.java
@@ -15,11 +15,11 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.version;
@@ -29,9 +29,9 @@ import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.QName;
/**
- * interface conating the constants used by the light weight
+ * interface conating the constants used by the light weight
* version store implementation
- *
+ *
* @author Roy Wetherall
*/
public interface VersionModel
@@ -40,20 +40,20 @@ public interface VersionModel
* Namespace
*/
public static final String NAMESPACE_URI = "http://www.alfresco.org/model/versionstore/1.0";
-
- /**
+
+ /**
* The store protocol
*/
public static final String STORE_PROTOCOL = VersionService.VERSION_STORE_PROTOCOL;
-
+
/**
* The store id
*/
public static final String STORE_ID = "lightWeightVersionStore";
-
-
+
+
public static final String PROP_VERSION_LABEL = "versionLabel";
- public static final String PROP_CREATED_DATE = ContentModel.PROP_CREATED.getLocalName();
+ public static final String PROP_CREATED_DATE = ContentModel.PROP_CREATED.getLocalName();
public static final String PROP_CREATOR = ContentModel.PROP_CREATOR.getLocalName();
public static final String PROP_VERSION_TYPE = "versionType";
public static final String PROP_VERSION_NUMBER = "versionNumber";
@@ -62,29 +62,29 @@ public interface VersionModel
public static final String PROP_FROZEN_NODE_STORE_PROTOCOL = "frozenNodeStoreProtocol";
public static final String PROP_FROZEN_NODE_STORE_ID = "frozenNodeStoreId";
public static final String PROP_FROZEN_ASPECTS = "frozenAspects";
-
+
/** The version store root aspect */
public static final QName ASPECT_VERSION_STORE_ROOT = QName.createQName(NAMESPACE_URI, "versionStoreRoot");
-
+
/**
* Version history type
*/
public static final String TYPE_VERSION_HISTORY = "versionHistory";
public static final QName TYPE_QNAME_VERSION_HISTORY = QName.createQName(NAMESPACE_URI, TYPE_VERSION_HISTORY);
-
+
/**
* Version history properties and associations
*/
public static final String PROP_VERSIONED_NODE_ID = "versionedNodeId";
- public static final QName PROP_QNAME_VERSIONED_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_VERSIONED_NODE_ID);
+ public static final QName PROP_QNAME_VERSIONED_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_VERSIONED_NODE_ID);
public static final QName ASSOC_ROOT_VERSION = QName.createQName(NAMESPACE_URI, "rootVersion");
-
+
/**
* Verison type
*/
public static final String TYPE_VERSION = "version";
public static final QName TYPE_QNAME_VERSION = QName.createQName(NAMESPACE_URI, TYPE_VERSION);
-
+
/**
* Version type properties and associations
*/
@@ -95,14 +95,14 @@ public interface VersionModel
public static final QName PROP_QNAME_FROZEN_NODE_STORE_PROTOCOL = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_STORE_PROTOCOL);
public static final QName PROP_QNAME_FROZEN_NODE_STORE_ID = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_STORE_ID);
public static final QName PROP_QNAME_FROZEN_ASPECTS = QName.createQName(NAMESPACE_URI, PROP_FROZEN_ASPECTS);
- public static final QName ASSOC_SUCCESSOR = QName.createQName(NAMESPACE_URI, "successor");
-
+ public static final QName ASSOC_SUCCESSOR = QName.createQName(NAMESPACE_URI, "successor");
+
/**
* Version Meta Data Value type
*/
public static final String TYPE_VERSION_META_DATA_VALUE = "versionMetaDataValue";
public static final QName TYPE_QNAME_VERSION_META_DATA_VALUE = QName.createQName(NAMESPACE_URI, TYPE_VERSION_META_DATA_VALUE);
-
+
/**
* Version Meta Data Value attributes
*/
@@ -110,13 +110,13 @@ public interface VersionModel
public static final QName PROP_QNAME_META_DATA_NAME = QName.createQName(NAMESPACE_URI, PROP_META_DATA_NAME);
public static final String PROP_META_DATA_VALUE = "metaDataValue";
public static final QName PROP_QNAME_META_DATA_VALUE = QName.createQName(NAMESPACE_URI, PROP_META_DATA_VALUE);
-
+
/**
* Versioned attribute type
*/
public static final String TYPE_VERSIONED_PROPERTY = "versionedProperty";
public static final QName TYPE_QNAME_VERSIONED_PROPERTY = QName.createQName(NAMESPACE_URI, TYPE_VERSIONED_PROPERTY);
-
+
/**
* Versioned attribute properties
*/
@@ -128,13 +128,13 @@ public interface VersionModel
public static final QName PROP_QNAME_VALUE = QName.createQName(NAMESPACE_URI, PROP_VALUE);
public static final QName PROP_QNAME_MULTI_VALUE = QName.createQName(NAMESPACE_URI, PROP_MULTI_VALUE);
public static final QName PROP_QNAME_IS_MULTI_VALUE = QName.createQName(NAMESPACE_URI, PROP_IS_MULTI_VALUE);
-
+
/**
* Versioned child assoc type
*/
public static final String TYPE_VERSIONED_CHILD_ASSOC = "versionedChildAssoc";
public static final QName TYPE_QNAME_VERSIONED_CHILD_ASSOC = QName.createQName(NAMESPACE_URI, TYPE_VERSIONED_CHILD_ASSOC);
-
+
/**
* Versioned child assoc properties
*/
@@ -146,13 +146,13 @@ public interface VersionModel
public static final QName PROP_QNAME_ASSOC_TYPE_QNAME = QName.createQName(NAMESPACE_URI, PROP_ASSOC_TYPE_QNAME);
public static final QName PROP_QNAME_IS_PRIMARY = QName.createQName(NAMESPACE_URI, PROP_IS_PRIMARY);
public static final QName PROP_QNAME_NTH_SIBLING = QName.createQName(NAMESPACE_URI, PROP_NTH_SIBLING);
-
+
/**
* Versioned assoc type
*/
public static final String TYPE_VERSIONED_ASSOC = "versionedAssoc";
public static final QName TYPE_QNAME_VERSIONED_ASSOC = QName.createQName(NAMESPACE_URI, TYPE_VERSIONED_ASSOC);
-
+
/**
* Child relationship names
*/
@@ -162,11 +162,18 @@ public interface VersionModel
public static final String CHILD_VERSIONED_CHILD_ASSOCS = "versionedChildAssocs";
public static final String CHILD_VERSIONED_ASSOCS = "versionedAssocs";
public static final String CHILD_VERSION_META_DATA = "versionMetaData";
-
+
public static final QName CHILD_QNAME_VERSION_HISTORIES = QName.createQName(NAMESPACE_URI, CHILD_VERSION_HISTORIES);
public static final QName CHILD_QNAME_VERSIONS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONS);
public static final QName CHILD_QNAME_VERSIONED_ATTRIBUTES = QName.createQName(NAMESPACE_URI, CHILD_VERSIONED_ATTRIBUTES);
public static final QName CHILD_QNAME_VERSIONED_CHILD_ASSOCS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONED_CHILD_ASSOCS);
public static final QName CHILD_QNAME_VERSIONED_ASSOCS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONED_ASSOCS);
public static final QName CHILD_QNAME_VERSION_META_DATA = QName.createQName(NAMESPACE_URI, CHILD_VERSION_META_DATA);
+
+ /**
+ * Created version associated to the deleted translations of an mlContainer
+ */
+ public static final String PROP_TRANSLATION_VERIONS = "translationVersions";
+ public static final QName PROP_QNAME_TRANSLATION_VERIONS = QName.createQName(VersionModel.NAMESPACE_URI, PROP_TRANSLATION_VERIONS);
+
}
diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java
index aa4323927e..7370373cdf 100644
--- a/source/java/org/alfresco/service/ServiceRegistry.java
+++ b/source/java/org/alfresco/service/ServiceRegistry.java
@@ -36,6 +36,7 @@ import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.ml.ContentFilterLanguagesService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.CopyService;
@@ -85,6 +86,7 @@ public interface ServiceRegistry
static final QName NODE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "NodeService");
static final QName CONTENT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ContentService");
static final QName MIMETYPE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "MimetypeService");
+ static final QName CONTENT_FILTER_LANGUAGES_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ContentFilterLanguagesService");
static final QName SEARCH_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "SearchService");
static final QName CATEGORY_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CategoryService");
static final QName COPY_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "CopyService");
@@ -179,6 +181,12 @@ public interface ServiceRegistry
@NotAuditable
MimetypeService getMimetypeService();
+ /**
+ * @return the content filter languages service (or null, if one is not provided)
+ */
+ @NotAuditable
+ ContentFilterLanguagesService getContentFilterLanguagesService();
+
/**
* @return the search service (or null, if one is not provided)
*/
diff --git a/source/java/org/alfresco/service/cmr/ml/ContentFilterLanguagesService.java b/source/java/org/alfresco/service/cmr/ml/ContentFilterLanguagesService.java
new file mode 100644
index 0000000000..f7c76bc102
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/ml/ContentFilterLanguagesService.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.service.cmr.ml;
+
+import java.util.List;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.Auditable;
+import org.alfresco.service.NotAuditable;
+import org.alfresco.service.PublicService;
+
+
+/**
+ * This service interface provides support for content filter languages .
+ *
+ * @author yanipig
+ *
+ */
+@PublicService
+public interface ContentFilterLanguagesService
+{
+
+ /**
+ * I18N message prefix to found the translation of a language label with a
+ * given lang code.
+ */
+ public static final String MESSAGE_PREFIX = "content_filter_lang.";
+
+ /**
+ * Get the order of the specified language code
+ *
+ * @param code
+ * @return
+ * @throws AlfrescoRuntimeException if the code doesn't exist
+ */
+ @NotAuditable
+ public int getOrderByCode(String code);
+
+ /**
+ * Get the language of the specified language code
+ *
+ * @param code
+ * @return
+ * @throws AlfrescoRuntimeException if the code doesn't exist
+ */
+ @NotAuditable
+ public String getLabelByCode(String code);
+
+ /**
+ * Get ordered list of languages code
+ *
+ * @return the map of displays indexed by extension
+ */
+ @Auditable
+ public List getFilterLanguages();
+
+ /**
+ * Get the the odered filter which results form an extract of availableLanguages on the filterLanguages
+ *
+ * @param availableLanguages the languages list whose will be removed from the filterLanguages
+ * @return
+ */
+ @Auditable
+ public List getMissingLanguages(List availableLanguages);
+
+ /**
+ * @return the default content filter language, null if it's not set.
+ */
+ @Auditable
+ public String getDefaultLanguage();
+
+ /**
+ * Since java.util.Locale
uses and returns old ISO code and the content-filter-lang.xml
+ * respects the new ones. This method convert new codes into old codes:
+ * (he, yi, and id) new codes to (iw, ji, and in) old codes
+ *
+ * @param code the ISO language code to convert
+ * @return the convertion of the codes he, yi, and id or the given code
+ */
+ @NotAuditable
+ public String convertToOldISOCode(String code);
+
+ /**
+ * Since java.util.Locale
uses and returns old ISO code and the content-filter-lang.xml
+ * respects the new ones. This method convert old codes into new codes:
+ * (iw, ji, and in) old codes to (he, yi, and id) new codes
+ *
+ * @param code the ISO language code to convert
+ * @return the convertion of the codes iw, ji, and in or the given code
+ */
+ @NotAuditable
+ public String convertToNewISOCode(String code);
+}
diff --git a/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java b/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
index f582e7fb18..ae94351f6f 100644
--- a/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
+++ b/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
@@ -24,6 +24,7 @@
*/
package org.alfresco.service.cmr.ml;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -117,4 +118,38 @@ public interface MultilingualContentService
*/
@Auditable(key = Auditable.Key.ARG_0, parameters = {"translationNodeRef", "locale"})
NodeRef getTranslationForLocale(NodeRef translationNodeRef, Locale locale);
+
+
+ /**
+ * Given cm:mlDocument or a cm:mlContainer, this node returns each
+ * locale that the node hasn't a translation yet.
+ *
+ * @param localizedNodeRef the cm:mlDocument or the cm:mlContainer
+ * @param addThisNodeLocale if true, add the locale of the given cm:mlDocument in the list.
+ * @return
+ */
+ @Auditable(key = Auditable.Key.ARG_0, parameters = {"localizedNodeRef", "addThisNodeLocale"})
+ List getMissingTranslations(NodeRef localizedNodeRef, boolean addThisNodeLocale);
+
+ /**
+ * Given any node, this returns the pivot translation. The pivot translation is the translation
+ * that its locale is referenced by the locale of the MLContainer. The translation can't be an
+ * empty translation.
+ *
+ * @param nodeRef the node to test
+ * @return the pivot translation
+ */
+ @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"})
+ NodeRef getPivotTranslation(NodeRef nodeRef);
+
+ /**
+ * Make a empty translation out of an existing document. The necessary translation structures will be created
+ * as necessary.
+ *
+ * @param translationOfNodeRef An existing cm:mlDocument or cm:mlContainer
+ * @param name The name of the translation to create
+ * @return Returns the new created cm:mlEmptyTranslation
+ */
+ @Auditable(key = Auditable.Key.ARG_0, parameters = {"translationOfNodeRef", "name", "locale"})
+ NodeRef addEmptyTranslation(NodeRef translationOfNodeRef, String name, Locale locale);
}