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 */