diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index c73d02bf66..47593b021e 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -323,7 +323,7 @@
-
+
@@ -845,7 +845,7 @@
-
+
@@ -855,7 +855,7 @@
-
+
diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml
index 69a0bc0d44..de1236d733 100644
--- a/config/alfresco/model-specific-services-context.xml
+++ b/config/alfresco/model-specific-services-context.xml
@@ -12,6 +12,7 @@
+
@@ -26,6 +27,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml
index 943d5ef240..430dcf6670 100644
--- a/config/alfresco/node-services-context.xml
+++ b/config/alfresco/node-services-context.xml
@@ -6,23 +6,33 @@
-
+
+
+
+
-
-
-
+
+
+ mlAwareNodeService
-
-
+
+
+ org.alfresco.service.cmr.repository.NodeService
+
+
+
+
+ mlPropertyInterceptor
+
-
-
+
+
org.alfresco.service.cmr.repository.NodeService
diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml
index eb46238ddf..0c726d9822 100644
--- a/config/alfresco/public-services-context.xml
+++ b/config/alfresco/public-services-context.xml
@@ -69,8 +69,6 @@
-
-
@@ -656,6 +654,7 @@
+
@@ -1166,6 +1165,8 @@
+ ${server.transaction.mode.readOnly}
+ ${server.transaction.mode.readOnly}
${server.transaction.mode.default}
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index 445e131556..39e629d276 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -537,6 +537,7 @@
org.alfresco.service.cmr.ml.MultilingualContentService.getTranslationForLocale=ACL_NODE.0.sys:base.Read
org.alfresco.service.cmr.ml.MultilingualContentService.getMissingTranslations=ACL_ALLOW
org.alfresco.service.cmr.ml.MultilingualContentService.getPivotTranslation=ACL_NODE.0.sys:base.Read
+ org.alfresco.service.cmr.ml.MultilingualContentService.isTranslation=ACL_NODE.0.sys:base.Read
org.alfresco.service.cmr.ml.MultilingualContentService.makeTranslation=ACL_NODE.0.sys:base.Write
org.alfresco.service.cmr.ml.MultilingualContentService.addTranslation=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.Write
org.alfresco.service.cmr.ml.MultilingualContentService.addEmptyTranslation=ACL_NODE.0.sys:base.Read
diff --git a/source/java/org/alfresco/repo/content/RoutingContentService.java b/source/java/org/alfresco/repo/content/RoutingContentService.java
index 117c78c182..f6f5c91860 100644
--- a/source/java/org/alfresco/repo/content/RoutingContentService.java
+++ b/source/java/org/alfresco/repo/content/RoutingContentService.java
@@ -27,13 +27,10 @@ package org.alfresco.repo.content;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
-import org.alfresco.model.ContentModel;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
@@ -50,7 +47,6 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
-import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -63,7 +59,6 @@ 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;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
@@ -316,66 +311,8 @@ public class RoutingContentService implements ContentService
// check that the URL is available
if (contentData == null || contentData.getContentUrl() == null)
{
- // if the node is an empty translation, the reader must be the reader of its pivot translation
- if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)
- && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
- {
- // NOTE : don't use the MultilingualContentService here because :
- // A valid SecureContext can't be provided in the RequestContext
-
- List parentAssocRefs = nodeService.getParentAssocs(
- nodeRef,
- ContentModel.ASSOC_MULTILINGUAL_CHILD,
- RegexQNamePattern.MATCH_ALL);
-
- if (parentAssocRefs.size() == 1)
- {
- // Get the ml container and its locale
- NodeRef container = parentAssocRefs.get(0).getParentRef();
- Locale containerLocale = (Locale) nodeService.getProperty(container, ContentModel.PROP_LOCALE);
-
- // Get each translation
- List assocRefs = nodeService.getChildAssocs(
- container,
- ContentModel.ASSOC_MULTILINGUAL_CHILD,
- RegexQNamePattern.MATCH_ALL);
-
-
- // found the pivot translation
- NodeRef pivot = null;
-
- for(ChildAssociationRef assoc : assocRefs)
- {
- pivot = assoc.getChildRef();
- Locale pivotLocale = (Locale) nodeService.getProperty(pivot, ContentModel.PROP_LOCALE);
-
- if(containerLocale.equals(pivotLocale))
- {
- break; // the pivot is set
- }
- else
- {
- pivot = null;
- }
- }
-
- // returns the reader of this pivot translation if it's different to
- // the node in parameter
-
- if (pivot != null && !nodeRef.getId().equals(pivot.getId()))
- {
- return getReader(pivot, propertyQName, fireContentReadPolicy);
- }
-
- }
- }
- else
- {
- // there is no URL - the interface specifies that this is not an error condition
- return null;
- }
-
-
+ // there is no URL - the interface specifies that this is not an error condition
+ return null;
}
String contentUrl = contentData.getContentUrl();
diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java
index beddcae02e..90d1fbe834 100644
--- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java
+++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java
@@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
@@ -37,6 +38,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
@@ -121,6 +123,7 @@ public class FileFolderServiceImpl implements FileFolderService
private CopyService copyService;
private SearchService searchService;
private ContentService contentService;
+ private MultilingualContentService multilingualContentService;
private MimetypeService mimetypeService;
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
@@ -164,6 +167,11 @@ public class FileFolderServiceImpl implements FileFolderService
this.contentService = contentService;
}
+ public void setMultilingualContentService(MultilingualContentService multilingualContentService)
+ {
+ this.multilingualContentService = multilingualContentService;
+ }
+
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
@@ -198,7 +206,7 @@ public class FileFolderServiceImpl implements FileFolderService
List results = new ArrayList(nodeRefs.size());
for (NodeRef nodeRef : nodeRefs)
{
- FileInfo fileInfo = toFileInfo(nodeRef);
+ FileInfo fileInfo = toFileInfo(nodeRef, true);
results.add(fileInfo);
}
return results;
@@ -207,7 +215,7 @@ public class FileFolderServiceImpl implements FileFolderService
/**
* Helper method to convert a node reference instance to a file info
*/
- private FileInfo toFileInfo(NodeRef nodeRef) throws InvalidTypeException
+ private FileInfo toFileInfo(NodeRef nodeRef, boolean addTranslations) throws InvalidTypeException
{
// get the file attributes
Map properties = nodeService.getProperties(nodeRef);
@@ -215,8 +223,33 @@ public class FileFolderServiceImpl implements FileFolderService
QName typeQName = nodeService.getType(nodeRef);
boolean isFolder = isFolder(typeQName);
+ Map translations = null;
+ if (!isFolder && addTranslations)
+ {
+ // Get any translations
+ translations = new HashMap(13);
+ // Check for the ML aspect
+ if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ // Get all the translations
+ Map translationsToConvert = multilingualContentService.getTranslations(nodeRef);
+ for (Map.Entry entry : translationsToConvert.entrySet())
+ {
+ Locale locale = entry.getKey();
+ NodeRef nodeRefToConvert = entry.getValue();
+ FileInfo convertedFileInfo = toFileInfo(nodeRefToConvert, false);
+ // Add to map
+ translations.put(locale, convertedFileInfo);
+ }
+ }
+ }
+ else
+ {
+ translations = Collections.emptyMap();
+ }
+
// construct the file info and add to the results
- FileInfo fileInfo = new FileInfoImpl(nodeRef, isFolder, properties);
+ FileInfo fileInfo = new FileInfoImpl(nodeRef, isFolder, properties, translations);
// done
return fileInfo;
}
@@ -542,7 +575,7 @@ public class FileFolderServiceImpl implements FileFolderService
private FileInfo moveOrCopy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName, boolean move) throws FileExistsException, FileNotFoundException
{
// get file/folder in its current state
- FileInfo beforeFileInfo = toFileInfo(sourceNodeRef);
+ FileInfo beforeFileInfo = toFileInfo(sourceNodeRef, true);
// check the name - null means keep the existing name
if (newName == null)
{
@@ -634,7 +667,7 @@ public class FileFolderServiceImpl implements FileFolderService
}
// get the details after the operation
- FileInfo afterFileInfo = toFileInfo(targetNodeRef);
+ FileInfo afterFileInfo = toFileInfo(targetNodeRef, true);
// done
if (logger.isDebugEnabled())
{
@@ -705,11 +738,11 @@ public class FileFolderServiceImpl implements FileFolderService
}
NodeRef nodeRef = assocRef.getChildRef();
- FileInfo fileInfo = toFileInfo(nodeRef);
+ FileInfo fileInfo = toFileInfo(nodeRef, true);
// done
if (logger.isDebugEnabled())
{
- FileInfo parentFileInfo = toFileInfo(parentNodeRef);
+ FileInfo parentFileInfo = toFileInfo(parentNodeRef, false);
logger.debug("Created: \n" +
" parent: " + parentFileInfo + "\n" +
" created: " + fileInfo);
@@ -755,7 +788,7 @@ public class FileFolderServiceImpl implements FileFolderService
}
}
// done
- FileInfo fileInfo = toFileInfo(currentParentRef);
+ FileInfo fileInfo = toFileInfo(currentParentRef, true);
return fileInfo;
}
@@ -790,7 +823,7 @@ public class FileFolderServiceImpl implements FileFolderService
continue;
}
// we found the root and expect to be building the path up
- FileInfo pathInfo = toFileInfo(childNodeRef);
+ FileInfo pathInfo = toFileInfo(childNodeRef, true);
results.add(pathInfo);
}
// check that we found the root
@@ -861,7 +894,7 @@ public class FileFolderServiceImpl implements FileFolderService
{
try
{
- return toFileInfo(nodeRef);
+ return toFileInfo(nodeRef, true);
}
catch (InvalidTypeException e)
{
@@ -871,7 +904,7 @@ public class FileFolderServiceImpl implements FileFolderService
public ContentReader getReader(NodeRef nodeRef)
{
- FileInfo fileInfo = toFileInfo(nodeRef);
+ FileInfo fileInfo = toFileInfo(nodeRef, false);
if (fileInfo.isFolder())
{
throw new InvalidTypeException("Unable to get a content reader for a folder: " + fileInfo);
@@ -881,7 +914,7 @@ public class FileFolderServiceImpl implements FileFolderService
public ContentWriter getWriter(NodeRef nodeRef)
{
- FileInfo fileInfo = toFileInfo(nodeRef);
+ FileInfo fileInfo = toFileInfo(nodeRef, false);
if (fileInfo.isFolder())
{
throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo);
diff --git a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java
index fe73f41f39..ce870f4215 100644
--- a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java
+++ b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java
@@ -25,7 +25,9 @@
package org.alfresco.repo.model.filefolder;
import java.io.Serializable;
+import java.util.Collections;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
import org.alfresco.model.ContentModel;
@@ -44,6 +46,7 @@ public class FileInfoImpl implements FileInfo
{
private NodeRef nodeRef;
private NodeRef linkNodeRef;
+ private Map translations;
private boolean isFolder;
private boolean isLink;
private Map properties;
@@ -52,13 +55,34 @@ public class FileInfoImpl implements FileInfo
* Package-level constructor
*/
/* package */ FileInfoImpl(NodeRef nodeRef, boolean isFolder, Map properties)
+ {
+ this(nodeRef, isFolder, properties, Collections.emptyMap());
+ }
+
+ /**
+ * Package-level constructor
+ *
+ * @param translations a map of translations including this instance. It may be null.
+ */
+ /* package */ FileInfoImpl(
+ NodeRef nodeRef,
+ boolean isFolder,
+ Map properties,
+ Map translations)
{
this.nodeRef = nodeRef;
this.isFolder = isFolder;
this.properties = properties;
+ if (translations == null || isFolder)
+ {
+ this.translations = Collections.emptyMap();
+ }
+ else
+ {
+ this.translations = translations;
+ }
// Check if this is a link node
-
if ( properties.containsKey( ContentModel.PROP_LINK_DESTINATION))
{
isLink = true;
@@ -66,6 +90,31 @@ public class FileInfoImpl implements FileInfo
}
}
+ /**
+ * @see #getNodeRef()
+ * @see NodeRef#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null || this.getClass().isInstance(obj))
+ {
+ return false;
+ }
+ FileInfoImpl that = (FileInfoImpl) obj;
+ return (this.getNodeRef().equals(that.getNodeRef()));
+ }
+
+ /**
+ * @see #getNodeRef()
+ * @see NodeRef#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ return getNodeRef().hashCode();
+ }
+
@Override
public String toString()
{
@@ -81,6 +130,8 @@ public class FileInfoImpl implements FileInfo
sb.append(linkNodeRef);
}
+ sb.append(", translations=").append(translations.size());
+
sb.append("]");
return sb.toString();
}
@@ -105,6 +156,11 @@ public class FileInfoImpl implements FileInfo
return linkNodeRef;
}
+ public Map getTranslations()
+ {
+ return translations;
+ }
+
public String getName()
{
return (String) properties.get(ContentModel.PROP_NAME);
diff --git a/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java
new file mode 100644
index 0000000000..0b605b6dc0
--- /dev/null
+++ b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java
@@ -0,0 +1,256 @@
+/*
+ * 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.filefolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+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.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * An interceptor that replaces files nodes with their equivalent
+ * translations according to the locale. It is to be used with the
+ * {@link FileFolderService}.
+ *
+ * @since 2.1
+ * @author Derek Hulley
+ */
+public class MLTranslationInterceptor implements MethodInterceptor
+{
+ /**
+ * Names of methods that return a List
or FileInfo
instances.
+ */
+ private static final Set METHOD_NAMES_LIST;
+ /**
+ * Names of methods that return a FileInfo
.
+ */
+ private static final Set METHOD_NAMES_SINGLE;
+ /**
+ * Names of methods that don't need interception. This is used to catch any new methods
+ * added to the interface.
+ */
+ private static final Set METHOD_NAMES_OTHER;
+ static
+ {
+ METHOD_NAMES_LIST = new HashSet(13);
+ METHOD_NAMES_LIST.add("list");
+ METHOD_NAMES_LIST.add("listFiles");
+ METHOD_NAMES_LIST.add("listFolders");
+ METHOD_NAMES_LIST.add("search");
+ METHOD_NAMES_LIST.add("getNamePath");
+
+ METHOD_NAMES_SINGLE = new HashSet(13);
+ METHOD_NAMES_SINGLE.add("searchSimple");
+ METHOD_NAMES_SINGLE.add("rename");
+ METHOD_NAMES_SINGLE.add("move");
+ METHOD_NAMES_SINGLE.add("copy");
+ METHOD_NAMES_SINGLE.add("create");
+ METHOD_NAMES_SINGLE.add("makeFolders");
+ METHOD_NAMES_SINGLE.add("getNamePath");
+ METHOD_NAMES_SINGLE.add("resolveNamePath");
+ METHOD_NAMES_SINGLE.add("getFileInfo");
+
+ METHOD_NAMES_OTHER = new HashSet(13);
+ METHOD_NAMES_OTHER.add("delete");
+ METHOD_NAMES_OTHER.add("getReader");
+ METHOD_NAMES_OTHER.add("getWriter");
+ }
+
+ private static Log logger = LogFactory.getLog(MLTranslationInterceptor.class);
+
+ private NodeService nodeService;
+ private MultilingualContentService multilingualContentService;
+
+ /**
+ * Constructor.
+ */
+ public MLTranslationInterceptor()
+ {
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setMultilingualContentService(MultilingualContentService multilingualContentService)
+ {
+ this.multilingualContentService = multilingualContentService;
+ }
+
+ /**
+ * Converts the node referenice where an alternative translation should be used.
+ *
+ * @param nodeRef the basic nodeRef
+ * @return Returns the replacement if required
+ */
+ private NodeRef getTranslatedNodeRef(NodeRef nodeRef)
+ {
+ // Ignore null
+ if (nodeRef == null)
+ {
+ return nodeRef;
+ }
+ // Ignore everything without the correct aspect
+ if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ return nodeRef;
+ }
+ // Find the translation
+ Map translations = multilingualContentService.getTranslations(nodeRef);
+ Locale filterLocale = I18NUtil.getContentLocaleOrNull();
+ Set possibleLocales = translations.keySet();
+ Locale localeToUse = I18NUtil.getNearestLocale(filterLocale, possibleLocales);
+ // Select the node
+ NodeRef translatedNodeRef = translations.get(localeToUse);
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ if (nodeRef.equals(translatedNodeRef))
+ {
+ logger.debug("NodeRef substitution: " + nodeRef + " --> " + translatedNodeRef);
+ }
+ else
+ {
+ logger.debug("NodeRef substitution: " + nodeRef + " (no change)");
+ }
+ }
+ return nodeRef;
+ }
+
+ /**
+ * Converts the file info where an alternative translation should be used.
+ *
+ * @param fileInfo the basic file or folder info
+ * @return Returns a replacement if required
+ *
+ * @see FileInfo#getTranslations()
+ */
+ private FileInfo getTranslatedFileInfo(FileInfo fileInfo)
+ {
+ // Ignore null
+ if (fileInfo == null)
+ {
+ return null;
+ }
+ // Ignore folders
+ if (fileInfo.isFolder())
+ {
+ return fileInfo;
+ }
+ // Ignore files without translations
+ Map translations = fileInfo.getTranslations();
+ if (translations.size() == 0)
+ {
+ return fileInfo;
+ }
+ // Get the locale to use
+ Set possibleLocales = translations.keySet();
+ Locale filterLocale = I18NUtil.getContentLocaleOrNull();
+ Locale localeToUse = I18NUtil.getNearestLocale(filterLocale, possibleLocales);
+ FileInfo translatedFileInfo = translations.get(localeToUse);
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ if (fileInfo.equals(translatedFileInfo))
+ {
+ logger.debug("FileInfo substitution: " + fileInfo + " --> " + translatedFileInfo);
+ }
+ else
+ {
+ logger.debug("FileInfo substitution: " + fileInfo + " (no change)");
+ }
+ }
+ return translatedFileInfo;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object invoke(MethodInvocation invocation) throws Throwable
+ {
+ Object ret = null;
+ String methodName = invocation.getMethod().getName();
+
+ if (METHOD_NAMES_LIST.contains(methodName))
+ {
+ List fileInfos = (List) invocation.proceed();
+ // Compile a set to ensure we don't get duplicates
+ Map translatedFileInfos = new HashMap(17);
+ for (FileInfo fileInfo : fileInfos)
+ {
+ FileInfo translatedFileInfo = getTranslatedFileInfo(fileInfo);
+ // Add this to the set
+ translatedFileInfos.put(fileInfo, translatedFileInfo);
+ }
+ // Convert the set back to a list
+ List orderedResults = new ArrayList(fileInfos.size());
+ for (FileInfo info : fileInfos)
+ {
+ orderedResults.add(translatedFileInfos.get(info));
+ }
+ ret = orderedResults;
+ }
+ else if (METHOD_NAMES_SINGLE.contains(methodName))
+ {
+ Object obj = invocation.proceed();
+ if (obj instanceof FileInfo)
+ {
+ FileInfo fileInfo = (FileInfo) obj;
+ ret = getTranslatedFileInfo(fileInfo);
+ }
+ else if (obj instanceof NodeRef)
+ {
+ NodeRef nodeRef = (NodeRef) obj;
+ ret = getTranslatedNodeRef(nodeRef);
+ }
+ }
+ else if (METHOD_NAMES_OTHER.contains(methodName))
+ {
+ // There is nothing to do
+ ret = invocation.proceed();
+ }
+ else
+ {
+ throw new RuntimeException("Method not handled by interceptor: " + methodName);
+ }
+
+ // Done
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java
index f80ae6c59a..303f558f0d 100644
--- a/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java
+++ b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java
@@ -50,8 +50,8 @@ import org.alfresco.service.namespace.QName;
*/
public class EmptyTranslationAspect implements
CopyServicePolicies.OnCopyNodePolicy,
- NodeServicePolicies.BeforeDeleteNodePolicy,
- NodeServicePolicies.OnRemoveAspectPolicy,
+// NodeServicePolicies.BeforeDeleteNodePolicy,
+// NodeServicePolicies.OnRemoveAspectPolicy,
ContentServicePolicies.OnContentUpdatePolicy
{
@@ -77,16 +77,16 @@ public class EmptyTranslationAspect implements
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"));
+//
+// 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"));
}
/**
@@ -105,7 +105,7 @@ public class EmptyTranslationAspect implements
{
this.nodeService = nodeService;
}
-
+
/**
* Copy a cm:mlEmptyTranslation is not permit.
*
@@ -123,38 +123,38 @@ public class EmptyTranslationAspect implements
*/
public void onContentUpdate(NodeRef nodeRef, boolean newContent)
{
- if(newContent)
+ if (newContent)
{
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
+ nodeService.removeAspect(nodeRef, ContentModel.ASPECT_TEMPORARY);
}
}
- /**
- * 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);
- }
- }
+// /**
+// * 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 be deleted.
+// * The other time the aspect is removed is when new content is added, in which case the node must be kept.
+// *
+// * @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)
+// {
+// // Delete the node if the content is empty.
+// // Keep the node if it has content
+// ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
+// if(contentData.getContentUrl() == 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
index 6be5a0a55d..250387f7c1 100644
--- a/source/java/org/alfresco/repo/model/ml/MLContainerType.java
+++ b/source/java/org/alfresco/repo/model/ml/MLContainerType.java
@@ -118,7 +118,7 @@ public class MLContainerType implements
Map translations = multilingualContentService.getTranslations(nodeRef);
// add the DELETION_RUNNING property
- nodeService.setProperty(nodeRef, PROP_NAME_DELETION_RUNNING, new Boolean(true));
+ nodeService.setProperty(nodeRef, PROP_NAME_DELETION_RUNNING, Boolean.TRUE);
for(Map.Entry entry : translations.entrySet())
{
@@ -126,10 +126,7 @@ public class MLContainerType implements
}
// remove the DELETION_RUNNING property
- Map prop = nodeService.getProperties(nodeRef);
- prop.remove(PROP_NAME_DELETION_RUNNING);
- nodeService.setProperties(nodeRef, prop);
-
+ nodeService.removeProperty(nodeRef, PROP_NAME_DELETION_RUNNING);
}
/**
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
index 2660cda30a..334c329f45 100644
--- a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java
@@ -245,6 +245,42 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
return mlContainerNodeRef;
}
+ /** @inheritDoc */
+ public boolean isTranslation(NodeRef contentNodeRef)
+ {
+ if (!nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ {
+ // It doesn't have the aspect, so it isn't a translation
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Document is not multilingual: " + contentNodeRef);
+ }
+ return false;
+ }
+ // Is there a ML container
+ List parentAssocRefs = nodeService.getParentAssocs(
+ contentNodeRef,
+ ContentModel.ASSOC_MULTILINGUAL_CHILD,
+ RegexQNamePattern.MATCH_ALL);
+ if (parentAssocRefs.size() > 0)
+ {
+ // It has the parent required
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Document has ML container parent: " + contentNodeRef);
+ }
+ return true;
+ }
+ else
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Document has no ML container parent: " + contentNodeRef);
+ }
+ return false;
+ }
+ }
+
/** @inheritDoc */
public NodeRef makeTranslation(NodeRef contentNodeRef, Locale locale)
{
@@ -505,7 +541,11 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
}
/**
- * @inheritDoc */
+ * @inheritDoc
+ *
+ * TODO: This logic merely creates a file with a specific aspect and is designed to support
+ * specific use-case in the UI. Examine if the logic should be here or in the UI.
+ */
public NodeRef addEmptyTranslation(NodeRef translationOfNodeRef, String name, Locale locale)
{
// any node used as reference
@@ -542,8 +582,11 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
// set it empty
nodeService.addAspect(newTranslationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, null);
+ // Initially, the file should be temporary. This will be changed as soon as some content is added.
+ nodeService.addAspect(newTranslationNodeRef, ContentModel.ASPECT_TEMPORARY, null);
// get the extension and set the ContentData property with an null URL.
+ // TODO: Mimetype must be correct, i.e. taken from the original
String extension = "";
int dotIdx;
if((dotIdx = name.lastIndexOf(".")) > -1 )
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImplTest.java b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImplTest.java
index fea798891e..0a1e956c14 100644
--- a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImplTest.java
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImplTest.java
@@ -141,12 +141,18 @@ public class MultilingualContentServiceImplTest extends TestCase
public void testMakeTranslation() throws Exception
{
NodeRef contentNodeRef = createContent();
+ // Check that it is not a translation
+ boolean isTranslation = multilingualContentService.isTranslation(contentNodeRef);
+ assertFalse("New content should not be a translation", isTranslation);
// 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());
+ // Check that it registers as a translation
+ isTranslation = multilingualContentService.isTranslation(contentNodeRef);
+ assertTrue("Content should be a translation", isTranslation);
}
public void testAddTranslationUsingContainer() throws Exception
diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java
index 846c8ed10b..82c33e7a10 100644
--- a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java
+++ b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java
@@ -149,21 +149,16 @@ public class MultilingualDocumentAspect implements
*/
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);
+ nodeService.removeProperty(destinationRef, ContentModel.PROP_LOCALE);
}
/**
- * 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)
+ * If the node is multilingual and it is the pivot translation, then deletion is not allowed unless the
*/
public void beforeDeleteNode(NodeRef nodeRef)
{
+// checkRemoveParentMLContainer(nodeRef);
+ // Remove the aspect
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
}
@@ -178,60 +173,52 @@ public class MultilingualDocumentAspect implements
*/
public void beforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
{
- if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ checkRemoveParentMLContainer(nodeRef);
+ }
+
+ private void checkRemoveParentMLContainer(NodeRef nodeRef)
+ {
+ // Avoid nodes that are no longer translations
+ if (!multilingualContentService.isTranslation(nodeRef))
{
- 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);
- }
- }
+ return;
+ }
+
+ 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)
+ {
+ // TODO: Is this still called? Can we get rid of the DELETION_RUNNING property?
+ 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)
+ * Removes the document's locale and the cm:mlEmptyTranslation aspect,
+ * if present.
*/
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
{
- if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
+ nodeService.removeProperty(nodeRef, ContentModel.PROP_LOCALE);
+ // Remove the empty translation aspect if it is present
+ if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
{
-
- 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);
-
- }
+ nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
}
}
diff --git a/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java b/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java
index af2f8f214e..7a843a4b9f 100644
--- a/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java
+++ b/source/java/org/alfresco/repo/model/ml/tools/EmptyTranslationAspectTest.java
@@ -119,8 +119,6 @@ public class EmptyTranslationAspectTest extends AbstractMultilingualTestCases {
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(),
diff --git a/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java b/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java
index d7b4a70148..7c1bdf37a4 100644
--- a/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java
+++ b/source/java/org/alfresco/repo/model/ml/tools/MultilingualDocumentAspectTest.java
@@ -82,15 +82,13 @@ public class MultilingualDocumentAspectTest extends AbstractMultilingualTestCase
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());
+ assertEquals("The restored 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());
+ assertEquals("The restored 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));
+ assertFalse("The restored 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
diff --git a/source/java/org/alfresco/repo/node/MLPropertyInterceptor.java b/source/java/org/alfresco/repo/node/MLPropertyInterceptor.java
index 9c12c8eee9..492c16e743 100644
--- a/source/java/org/alfresco/repo/node/MLPropertyInterceptor.java
+++ b/source/java/org/alfresco/repo/node/MLPropertyInterceptor.java
@@ -30,9 +30,11 @@ import java.util.Locale;
import java.util.Map;
import org.alfresco.i18n.I18NUtil;
+import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -72,6 +74,8 @@ public class MLPropertyInterceptor implements MethodInterceptor
/** Direct access to the NodeService */
private NodeService directNodeService;
+ /** Direct access to the ML Content Service */
+ private MultilingualContentService directMultilingualContentService;
/** Used to access property definitions */
private DictionaryService dictionaryService;
@@ -109,6 +113,11 @@ public class MLPropertyInterceptor implements MethodInterceptor
this.directNodeService = bean;
}
+ public void setDirectMultilingualContentService(MultilingualContentService directMultilingualContentService)
+ {
+ this.directMultilingualContentService = directMultilingualContentService;
+ }
+
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
@@ -136,46 +145,29 @@ public class MLPropertyInterceptor implements MethodInterceptor
// Don't interfere
return invocation.proceed();
}
-
+
if (methodName.equals("getProperty"))
{
- ret = invocation.proceed();
- // The return value might need to be converted to a String
- if (ret != null && ret instanceof MLText)
- {
- MLText mlText = (MLText) ret;
- ret = mlText.getClosestValue(contentLocale);
- // done
- if (logger.isDebugEnabled())
- {
- logger.debug(
- "Converted ML text: \n" +
- " initial: " + mlText + "\n" +
- " converted: " + ret);
- }
- }
+ NodeRef nodeRef = (NodeRef) args[0];
+ QName propertyQName = (QName) args[1];
+
+ Serializable value = (Serializable) invocation.proceed();
+ ret = convertOutboundProperty(contentLocale, nodeRef, propertyQName, value);
}
else if (methodName.equals("getProperties"))
{
+ NodeRef nodeRef = (NodeRef) args[0];
+
Map properties = (Map) invocation.proceed();
Map convertedProperties = new HashMap(properties.size() * 2);
// Check each return value type
for (Map.Entry entry : properties.entrySet())
{
- QName key = entry.getKey();
+ QName propertyQName = entry.getKey();
Serializable value = entry.getValue();
- if (value != null && value instanceof MLText)
- {
- MLText mlText = (MLText) value;
- value = mlText.getClosestValue(contentLocale);
- // Store the converted value
- convertedProperties.put(key, value);
- }
- else
- {
- // The value goes straight back in
- convertedProperties.put(key, value);
- }
+ Serializable convertedValue = convertOutboundProperty(contentLocale, nodeRef, propertyQName, value);
+ // Add it to the return map
+ convertedProperties.put(propertyQName, convertedValue);
}
ret = convertedProperties;
// Done
@@ -212,7 +204,6 @@ public class MLPropertyInterceptor implements MethodInterceptor
}
else if (methodName.equals("setProperty"))
{
- //check if the property is of type MLText
NodeRef nodeRef = (NodeRef) args[0];
QName propertyQName = (QName) args[1];
Serializable inboundValue = (Serializable) args[2];
@@ -232,6 +223,64 @@ public class MLPropertyInterceptor implements MethodInterceptor
return ret;
}
+ /**
+ * Ensure that content is spoofed for empty translations.
+ */
+ private Serializable convertOutboundProperty(
+ Locale contentLocale,
+ NodeRef nodeRef,
+ QName propertyQName,
+ Serializable outboundValue)
+ {
+ Serializable ret = null;
+ PropertyDefinition propertyDef = this.dictionaryService.getProperty(propertyQName);
+ // Is it content?
+ if (propertyDef != null && propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))
+ {
+ // Check if the document is an empty translation
+ if (directNodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
+ {
+ // Ignore the value and take it directly from the pivot translation
+ NodeRef pivotNodeRef = directMultilingualContentService.getPivotTranslation(nodeRef);
+ if (pivotNodeRef == null)
+ {
+ // This is very bad, but we don't fail the server for it
+ logger.warn("No pivot translation found for empty translation: " + nodeRef);
+ ret = outboundValue;
+ }
+ else
+ {
+ // Get the corresponding property from the pivot
+ ret = directNodeService.getProperty(pivotNodeRef, propertyQName);
+ }
+ }
+ else
+ {
+ ret = outboundValue;
+ }
+ }
+ else if (outboundValue != null && outboundValue instanceof MLText)
+ {
+ MLText mlText = (MLText) outboundValue;
+ ret = mlText.getClosestValue(contentLocale);
+ }
+ else
+ {
+ ret = outboundValue;
+ }
+ // Done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Converted outbound property: \n" +
+ " NodeRef: " + nodeRef + "\n" +
+ " Property: " + propertyQName + "\n" +
+ " Before: " + outboundValue + "\n" +
+ " After: " + ret);
+ }
+ return ret;
+ }
+
/**
*
* @param inboundValue The value that must be set
diff --git a/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java b/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java
deleted file mode 100644
index 40ef961d79..0000000000
--- a/source/java/org/alfresco/repo/node/MLTranslationInterceptor.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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 directNodeService;
-
- 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
- && directNodeService.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 = directNodeService.getType(child);
-
- if(type.equals(ContentModel.TYPE_CONTENT) &&
- directNodeService.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 void setMultilingualContentService(
- MultilingualContentService multilingualContentService)
- {
- this.multilingualContentService = multilingualContentService;
- }
-
- public void setDirectNodeService(NodeService nodeService)
- {
- this.directNodeService = nodeService;
- }
-}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
index e4e3f3698a..bf039646ba 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
@@ -694,7 +694,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// First get the node to ensure that it exists
Node node = getNodeNotNull(nodeRef);
- boolean isArchivedNode = false;
boolean requiresDelete = false;
// Invoke policy behaviours
@@ -713,7 +712,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// the node has the temporary aspect meaning
// it can not be archived
requiresDelete = true;
- isArchivedNode = false;
}
else
{
@@ -731,17 +729,15 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
{
// perform a normal deletion
nodeDaoService.deleteNode(node, true);
- isArchivedNode = false;
+ // Invoke policy behaviours
+ invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, false);
}
else
{
// archive it
archiveNode(nodeRef, archiveStoreRef);
- isArchivedNode = true;
+ // The archive performs a move, which will fire the appropriate OnDeleteNode
}
-
- // Invoke policy behaviours
- invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode);
}
public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java
index 2d93aa10e0..a59b4274f1 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java
@@ -66,7 +66,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
protected NodeService getNodeService()
{
- return (NodeService) applicationContext.getBean("nodeService");
+ return (NodeService) applicationContext.getBean("dbNodeService");
}
@Override
diff --git a/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java b/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
index ae94351f6f..ff4f3c7304 100644
--- a/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
+++ b/source/java/org/alfresco/service/cmr/ml/MultilingualContentService.java
@@ -52,6 +52,15 @@ public interface MultilingualContentService
@Auditable(key = Auditable.Key.ARG_0, parameters = {"localizedNodeRef"})
void renameWithMLExtension(NodeRef localizedNodeRef);
+ /**
+ * Checks whether an existing document is part of a translation group.
+ *
+ * @param contentNodeRef An existing cm:content
+ * @return Returns true if the document has a cm:mlContainer parent
+ */
+ @Auditable(key = Auditable.Key.ARG_0, parameters = {"contentNodeRef"})
+ boolean isTranslation(NodeRef contentNodeRef);
+
/**
* Make an existing document into a translation by adding the cm:mlDocument aspect and
* creating a cm:mlContainer parent. If it is already a translation, then nothing is done.
@@ -132,12 +141,14 @@ public interface MultilingualContentService
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.
+ * Given any node, this returns the pivot translation. All multilingual documents belong to
+ * a group linked by a hidden parent node of type cm:mlContainer. The pivot language
+ * for the translations is stored on the parent, and the child that has the same locale is the
+ * pivot translation.
*
- * @param nodeRef the node to test
- * @return the pivot translation
+ * @param nodeRef a cm:mlDocument
+ * @return Returns a corresponding cm:mlDocument that matches the locale of
+ * of the cm:mlContainer.
*/
@Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"})
NodeRef getPivotTranslation(NodeRef nodeRef);
diff --git a/source/java/org/alfresco/service/cmr/model/FileInfo.java b/source/java/org/alfresco/service/cmr/model/FileInfo.java
index 9697ad3bd3..3764a1f4dc 100644
--- a/source/java/org/alfresco/service/cmr/model/FileInfo.java
+++ b/source/java/org/alfresco/service/cmr/model/FileInfo.java
@@ -26,6 +26,7 @@ package org.alfresco.service.cmr.model;
import java.io.Serializable;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
import org.alfresco.service.cmr.repository.ContentData;
@@ -61,6 +62,15 @@ public interface FileInfo
*/
public NodeRef getLinkNodeRef();
+ /**
+ * Get all translated versions of the file info. The map will always be empty if this
+ * instance references {@link #isFolder() a folder}. The map may also be empty if the
+ * file represented is not multilingual.
+ *
+ * @return Returns a map of transalations keyed on locale
+ */
+ public Map getTranslations();
+
/**
* @return Returns the name of the file or folder within the parent folder
*/