Merging from EC-MC: Checkpoint before refactor

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5744 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-05-22 05:03:16 +00:00
parent 2d461f5dd9
commit d818c54e99
22 changed files with 644 additions and 416 deletions

View File

@@ -323,7 +323,7 @@
<bean id="admLuceneIndexerAndSearcherFactory" <bean id="admLuceneIndexerAndSearcherFactory"
class="org.alfresco.repo.search.impl.lucene.ADMLuceneIndexerAndSearcherFactory"> class="org.alfresco.repo.search.impl.lucene.ADMLuceneIndexerAndSearcherFactory">
<property name="nodeService"> <property name="nodeService">
<ref bean="nodeService" /> <ref bean="mlAwareNodeService" />
</property> </property>
<property name="dictionaryService"> <property name="dictionaryService">
<ref bean="dictionaryService" /> <ref bean="dictionaryService" />
@@ -845,7 +845,7 @@
<ref bean="policyComponent" /> <ref bean="policyComponent" />
</property> </property>
<property name="nodeService"> <property name="nodeService">
<ref bean="nodeService" /> <ref bean="mlAwareNodeService" />
</property> </property>
</bean> </bean>
@@ -855,7 +855,7 @@
<ref bean="policyComponent" /> <ref bean="policyComponent" />
</property> </property>
<property name="nodeService"> <property name="nodeService">
<ref bean="nodeService" /> <ref bean="mlAwareNodeService" />
</property> </property>
<property name="multilingualContentService"> <property name="multilingualContentService">
<ref bean="multilingualContentService" /> <ref bean="multilingualContentService" />

View File

@@ -12,6 +12,7 @@
<property name="copyService"><ref bean="copyService" /></property> <property name="copyService"><ref bean="copyService" /></property>
<property name="searchService"><ref bean="searchService" /></property> <property name="searchService"><ref bean="searchService" /></property>
<property name="contentService"><ref bean="contentService" /></property> <property name="contentService"><ref bean="contentService" /></property>
<property name="multilingualContentService"><ref bean="multilingualContentService" /></property>
<property name="mimetypeService"><ref bean="mimetypeService" /></property> <property name="mimetypeService"><ref bean="mimetypeService" /></property>
<property name="systemPaths"> <property name="systemPaths">
@@ -27,6 +28,16 @@
</property> </property>
</bean> </bean>
<bean id="mlTranslationInterceptor" class="org.alfresco.repo.model.filefolder.MLTranslationInterceptor" >
<property name="nodeService">
<ref bean="nodeService"/>
</property>
<property name="multilingualContentService">
<ref bean="multilingualContentService"/>
</property>
</bean>
<bean name="tempFileMarkerInterceptor" class="org.alfresco.repo.model.filefolder.TempFileMarkerInterceptor"> <bean name="tempFileMarkerInterceptor" class="org.alfresco.repo.model.filefolder.TempFileMarkerInterceptor">
<property name="nodeService"> <property name="nodeService">
<ref bean="nodeService" /> <ref bean="nodeService" />

View File

@@ -6,23 +6,33 @@
<bean id="mlPropertyInterceptor" class="org.alfresco.repo.node.MLPropertyInterceptor"> <bean id="mlPropertyInterceptor" class="org.alfresco.repo.node.MLPropertyInterceptor">
<property name="directNodeService"> <property name="directNodeService">
<ref bean="nodeService"></ref> <ref bean="mlAwareNodeService" />
</property>
<property name="directMultilingualContentService">
<ref bean="multilingualContentService" />
</property> </property>
<property name="dictionaryService"> <property name="dictionaryService">
<ref bean="dictionaryService" /> <ref bean="dictionaryService" />
</property> </property>
</bean> </bean>
<bean id="mlTranslationInterceptor" class="org.alfresco.repo.node.MLTranslationInterceptor"> <bean id="nodeService" class="org.springframework.aop.framework.ProxyFactoryBean" >
<property name="directNodeService"> <property name="targetName">
<ref bean="nodeService"/> <value>mlAwareNodeService</value>
</property> </property>
<property name="multilingualContentService"> <property name="proxyInterfaces">
<ref bean="multilingualContentService"/> <list>
<value>org.alfresco.service.cmr.repository.NodeService</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>mlPropertyInterceptor</value>
</list>
</property> </property>
</bean> </bean>
<bean id="nodeService" class="org.alfresco.repo.service.StoreRedirectorProxyFactory"> <bean id="mlAwareNodeService" class="org.alfresco.repo.service.StoreRedirectorProxyFactory">
<property name="proxyInterface"> <property name="proxyInterface">
<value>org.alfresco.service.cmr.repository.NodeService</value> <value>org.alfresco.service.cmr.repository.NodeService</value>
</property> </property>

View File

@@ -69,8 +69,6 @@
<idref local="AuditMethodInterceptor"/> <idref local="AuditMethodInterceptor"/>
<idref local="exceptionTranslator"/> <idref local="exceptionTranslator"/>
<idref bean="NodeService_security"/> <idref bean="NodeService_security"/>
<idref bean="mlTranslationInterceptor"/>
<idref bean="mlPropertyInterceptor"/>
</list> </list>
</property> </property>
</bean> </bean>
@@ -656,6 +654,7 @@
<idref local="AuditMethodInterceptor"/> <idref local="AuditMethodInterceptor"/>
<idref local="exceptionTranslator"/> <idref local="exceptionTranslator"/>
<idref bean="FileFolderService_security"/> <idref bean="FileFolderService_security"/>
<idref bean="mlTranslationInterceptor"/>
<idref bean="tempFileMarkerInterceptor"/> <idref bean="tempFileMarkerInterceptor"/>
</list> </list>
</property> </property>
@@ -1166,6 +1165,8 @@
</property> </property>
<property name="transactionAttributes"> <property name="transactionAttributes">
<props> <props>
<prop key="is*">${server.transaction.mode.readOnly}</prop>
<prop key="get*">${server.transaction.mode.readOnly}</prop>
<prop key="*">${server.transaction.mode.default}</prop> <prop key="*">${server.transaction.mode.default}</prop>
</props> </props>
</property> </property>

View File

@@ -537,6 +537,7 @@
org.alfresco.service.cmr.ml.MultilingualContentService.getTranslationForLocale=ACL_NODE.0.sys:base.Read 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.getMissingTranslations=ACL_ALLOW
org.alfresco.service.cmr.ml.MultilingualContentService.getPivotTranslation=ACL_NODE.0.sys:base.Read 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.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.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 org.alfresco.service.cmr.ml.MultilingualContentService.addEmptyTranslation=ACL_NODE.0.sys:base.Read

View File

@@ -27,13 +27,10 @@ package org.alfresco.repo.content;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; 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.DictionaryService;
import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.dictionary.PropertyDefinition; 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.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader; 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.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.EqualsHelper; import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
@@ -316,66 +311,8 @@ public class RoutingContentService implements ContentService
// check that the URL is available // check that the URL is available
if (contentData == null || contentData.getContentUrl() == null) if (contentData == null || contentData.getContentUrl() == null)
{ {
// if the node is an empty translation, the reader must be the reader of its pivot translation // there is no URL - the interface specifies that this is not an error condition
if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT) return null;
&& 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<ChildAssociationRef> 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<ChildAssociationRef> 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;
}
} }
String contentUrl = contentData.getContentUrl(); String contentUrl = contentData.getContentUrl();

View File

@@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
@@ -37,6 +38,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; 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.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileInfo;
@@ -121,6 +123,7 @@ public class FileFolderServiceImpl implements FileFolderService
private CopyService copyService; private CopyService copyService;
private SearchService searchService; private SearchService searchService;
private ContentService contentService; private ContentService contentService;
private MultilingualContentService multilingualContentService;
private MimetypeService mimetypeService; private MimetypeService mimetypeService;
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) // 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; this.contentService = contentService;
} }
public void setMultilingualContentService(MultilingualContentService multilingualContentService)
{
this.multilingualContentService = multilingualContentService;
}
public void setMimetypeService(MimetypeService mimetypeService) public void setMimetypeService(MimetypeService mimetypeService)
{ {
this.mimetypeService = mimetypeService; this.mimetypeService = mimetypeService;
@@ -198,7 +206,7 @@ public class FileFolderServiceImpl implements FileFolderService
List<FileInfo> results = new ArrayList<FileInfo>(nodeRefs.size()); List<FileInfo> results = new ArrayList<FileInfo>(nodeRefs.size());
for (NodeRef nodeRef : nodeRefs) for (NodeRef nodeRef : nodeRefs)
{ {
FileInfo fileInfo = toFileInfo(nodeRef); FileInfo fileInfo = toFileInfo(nodeRef, true);
results.add(fileInfo); results.add(fileInfo);
} }
return results; return results;
@@ -207,7 +215,7 @@ public class FileFolderServiceImpl implements FileFolderService
/** /**
* Helper method to convert a node reference instance to a file info * 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 // get the file attributes
Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
@@ -215,8 +223,33 @@ public class FileFolderServiceImpl implements FileFolderService
QName typeQName = nodeService.getType(nodeRef); QName typeQName = nodeService.getType(nodeRef);
boolean isFolder = isFolder(typeQName); boolean isFolder = isFolder(typeQName);
Map<Locale, FileInfo> translations = null;
if (!isFolder && addTranslations)
{
// Get any translations
translations = new HashMap<Locale, FileInfo>(13);
// Check for the ML aspect
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
{
// Get all the translations
Map<Locale, NodeRef> translationsToConvert = multilingualContentService.getTranslations(nodeRef);
for (Map.Entry<Locale, NodeRef> 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.<Locale, FileInfo>emptyMap();
}
// construct the file info and add to the results // 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 // done
return fileInfo; 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 private FileInfo moveOrCopy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName, boolean move) throws FileExistsException, FileNotFoundException
{ {
// get file/folder in its current state // 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 // check the name - null means keep the existing name
if (newName == null) if (newName == null)
{ {
@@ -634,7 +667,7 @@ public class FileFolderServiceImpl implements FileFolderService
} }
// get the details after the operation // get the details after the operation
FileInfo afterFileInfo = toFileInfo(targetNodeRef); FileInfo afterFileInfo = toFileInfo(targetNodeRef, true);
// done // done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
@@ -705,11 +738,11 @@ public class FileFolderServiceImpl implements FileFolderService
} }
NodeRef nodeRef = assocRef.getChildRef(); NodeRef nodeRef = assocRef.getChildRef();
FileInfo fileInfo = toFileInfo(nodeRef); FileInfo fileInfo = toFileInfo(nodeRef, true);
// done // done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
FileInfo parentFileInfo = toFileInfo(parentNodeRef); FileInfo parentFileInfo = toFileInfo(parentNodeRef, false);
logger.debug("Created: \n" + logger.debug("Created: \n" +
" parent: " + parentFileInfo + "\n" + " parent: " + parentFileInfo + "\n" +
" created: " + fileInfo); " created: " + fileInfo);
@@ -755,7 +788,7 @@ public class FileFolderServiceImpl implements FileFolderService
} }
} }
// done // done
FileInfo fileInfo = toFileInfo(currentParentRef); FileInfo fileInfo = toFileInfo(currentParentRef, true);
return fileInfo; return fileInfo;
} }
@@ -790,7 +823,7 @@ public class FileFolderServiceImpl implements FileFolderService
continue; continue;
} }
// we found the root and expect to be building the path up // we found the root and expect to be building the path up
FileInfo pathInfo = toFileInfo(childNodeRef); FileInfo pathInfo = toFileInfo(childNodeRef, true);
results.add(pathInfo); results.add(pathInfo);
} }
// check that we found the root // check that we found the root
@@ -861,7 +894,7 @@ public class FileFolderServiceImpl implements FileFolderService
{ {
try try
{ {
return toFileInfo(nodeRef); return toFileInfo(nodeRef, true);
} }
catch (InvalidTypeException e) catch (InvalidTypeException e)
{ {
@@ -871,7 +904,7 @@ public class FileFolderServiceImpl implements FileFolderService
public ContentReader getReader(NodeRef nodeRef) public ContentReader getReader(NodeRef nodeRef)
{ {
FileInfo fileInfo = toFileInfo(nodeRef); FileInfo fileInfo = toFileInfo(nodeRef, false);
if (fileInfo.isFolder()) if (fileInfo.isFolder())
{ {
throw new InvalidTypeException("Unable to get a content reader for a folder: " + fileInfo); 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) public ContentWriter getWriter(NodeRef nodeRef)
{ {
FileInfo fileInfo = toFileInfo(nodeRef); FileInfo fileInfo = toFileInfo(nodeRef, false);
if (fileInfo.isFolder()) if (fileInfo.isFolder())
{ {
throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo); throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo);

View File

@@ -25,7 +25,9 @@
package org.alfresco.repo.model.filefolder; package org.alfresco.repo.model.filefolder;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
@@ -44,6 +46,7 @@ public class FileInfoImpl implements FileInfo
{ {
private NodeRef nodeRef; private NodeRef nodeRef;
private NodeRef linkNodeRef; private NodeRef linkNodeRef;
private Map<Locale, FileInfo> translations;
private boolean isFolder; private boolean isFolder;
private boolean isLink; private boolean isLink;
private Map<QName, Serializable> properties; private Map<QName, Serializable> properties;
@@ -52,13 +55,34 @@ public class FileInfoImpl implements FileInfo
* Package-level constructor * Package-level constructor
*/ */
/* package */ FileInfoImpl(NodeRef nodeRef, boolean isFolder, Map<QName, Serializable> properties) /* package */ FileInfoImpl(NodeRef nodeRef, boolean isFolder, Map<QName, Serializable> properties)
{
this(nodeRef, isFolder, properties, Collections.<Locale, FileInfo>emptyMap());
}
/**
* Package-level constructor
*
* @param translations a map of translations including this instance. It may be null.
*/
/* package */ FileInfoImpl(
NodeRef nodeRef,
boolean isFolder,
Map<QName, Serializable> properties,
Map<Locale, FileInfo> translations)
{ {
this.nodeRef = nodeRef; this.nodeRef = nodeRef;
this.isFolder = isFolder; this.isFolder = isFolder;
this.properties = properties; this.properties = properties;
if (translations == null || isFolder)
{
this.translations = Collections.<Locale, FileInfo>emptyMap();
}
else
{
this.translations = translations;
}
// Check if this is a link node // Check if this is a link node
if ( properties.containsKey( ContentModel.PROP_LINK_DESTINATION)) if ( properties.containsKey( ContentModel.PROP_LINK_DESTINATION))
{ {
isLink = true; 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 @Override
public String toString() public String toString()
{ {
@@ -81,6 +130,8 @@ public class FileInfoImpl implements FileInfo
sb.append(linkNodeRef); sb.append(linkNodeRef);
} }
sb.append(", translations=").append(translations.size());
sb.append("]"); sb.append("]");
return sb.toString(); return sb.toString();
} }
@@ -105,6 +156,11 @@ public class FileInfoImpl implements FileInfo
return linkNodeRef; return linkNodeRef;
} }
public Map<Locale, FileInfo> getTranslations()
{
return translations;
}
public String getName() public String getName()
{ {
return (String) properties.get(ContentModel.PROP_NAME); return (String) properties.get(ContentModel.PROP_NAME);

View File

@@ -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 <code>List</code> or <code>FileInfo</code> instances.
*/
private static final Set<String> METHOD_NAMES_LIST;
/**
* Names of methods that return a <code>FileInfo</code>.
*/
private static final Set<String> 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<String> METHOD_NAMES_OTHER;
static
{
METHOD_NAMES_LIST = new HashSet<String>(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<String>(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<String>(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<Locale, NodeRef> translations = multilingualContentService.getTranslations(nodeRef);
Locale filterLocale = I18NUtil.getContentLocaleOrNull();
Set<Locale> 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<Locale, FileInfo> translations = fileInfo.getTranslations();
if (translations.size() == 0)
{
return fileInfo;
}
// Get the locale to use
Set<Locale> 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<FileInfo> fileInfos = (List<FileInfo>) invocation.proceed();
// Compile a set to ensure we don't get duplicates
Map<FileInfo, FileInfo> translatedFileInfos = new HashMap<FileInfo, FileInfo>(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<FileInfo> orderedResults = new ArrayList<FileInfo>(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;
}
}

View File

@@ -50,8 +50,8 @@ import org.alfresco.service.namespace.QName;
*/ */
public class EmptyTranslationAspect implements public class EmptyTranslationAspect implements
CopyServicePolicies.OnCopyNodePolicy, CopyServicePolicies.OnCopyNodePolicy,
NodeServicePolicies.BeforeDeleteNodePolicy, // NodeServicePolicies.BeforeDeleteNodePolicy,
NodeServicePolicies.OnRemoveAspectPolicy, // NodeServicePolicies.OnRemoveAspectPolicy,
ContentServicePolicies.OnContentUpdatePolicy ContentServicePolicies.OnContentUpdatePolicy
{ {
@@ -77,16 +77,16 @@ public class EmptyTranslationAspect implements
QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"), QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"),
ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
new JavaBehaviour(this, "onContentUpdate")); new JavaBehaviour(this, "onContentUpdate"));
//
this.policyComponent.bindClassBehaviour( // this.policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), // QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, // ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
new JavaBehaviour(this, "beforeDeleteNode")); // new JavaBehaviour(this, "beforeDeleteNode"));
//
this.policyComponent.bindClassBehaviour( // this.policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), // QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, // ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION,
new JavaBehaviour(this, "onRemoveAspect")); // new JavaBehaviour(this, "onRemoveAspect"));
} }
/** /**
@@ -123,38 +123,38 @@ public class EmptyTranslationAspect implements
*/ */
public void onContentUpdate(NodeRef nodeRef, boolean newContent) public void onContentUpdate(NodeRef nodeRef, boolean newContent)
{ {
if(newContent) if (newContent)
{ {
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION); nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION);
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_TEMPORARY);
} }
} }
/** // /**
* If a <b>cm:mlEmptyTranslation<b> is deleted, it can't be archived. // * If a <b>cm:mlEmptyTranslation<b> is deleted, it can't be archived.
* // *
* @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef) // * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
*/ // */
public void beforeDeleteNode(NodeRef nodeRef) // public void beforeDeleteNode(NodeRef nodeRef)
{ // {
// add TEMPORARY ASPECT to force the deleteNode // // add TEMPORARY ASPECT to force the deleteNode
nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); // nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);
} // }
//
/** // /**
* If the aspect <b>cm:mlEmptyTranslation<b> is removed <b>and the content url property is null</b>, the node can't be store much time. // * If the aspect <b>cm:mlEmptyTranslation<b> is removed <b>and the content url property is null</b>, 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) * // *
*/ // * @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) // */
{ // 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. // // Delete the node if the content is empty.
if(aspectTypeQName.equals(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION) // // Keep the node if it has content
&& ((ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT)).getContentUrl() == null) // ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
{ // if(contentData.getContentUrl() == null)
// add TEMPORARY ASPECT to force the deleteNode // {
nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); // nodeService.deleteNode(nodeRef);
nodeService.deleteNode(nodeRef); // }
} // }
}
} }

View File

@@ -118,7 +118,7 @@ public class MLContainerType implements
Map<Locale, NodeRef> translations = multilingualContentService.getTranslations(nodeRef); Map<Locale, NodeRef> translations = multilingualContentService.getTranslations(nodeRef);
// add the DELETION_RUNNING property // 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<Locale, NodeRef> entry : translations.entrySet()) for(Map.Entry<Locale, NodeRef> entry : translations.entrySet())
{ {
@@ -126,10 +126,7 @@ public class MLContainerType implements
} }
// remove the DELETION_RUNNING property // remove the DELETION_RUNNING property
Map<QName, Serializable> prop = nodeService.getProperties(nodeRef); nodeService.removeProperty(nodeRef, PROP_NAME_DELETION_RUNNING);
prop.remove(PROP_NAME_DELETION_RUNNING);
nodeService.setProperties(nodeRef, prop);
} }
/** /**

View File

@@ -245,6 +245,42 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
return mlContainerNodeRef; 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<ChildAssociationRef> 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 */ /** @inheritDoc */
public NodeRef makeTranslation(NodeRef contentNodeRef, Locale locale) 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) public NodeRef addEmptyTranslation(NodeRef translationOfNodeRef, String name, Locale locale)
{ {
// any node used as reference // any node used as reference
@@ -542,8 +582,11 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic
// set it empty // set it empty
nodeService.addAspect(newTranslationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, null); 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. // 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 = ""; String extension = "";
int dotIdx; int dotIdx;
if((dotIdx = name.lastIndexOf(".")) > -1 ) if((dotIdx = name.lastIndexOf(".")) > -1 )

View File

@@ -141,12 +141,18 @@ public class MultilingualContentServiceImplTest extends TestCase
public void testMakeTranslation() throws Exception public void testMakeTranslation() throws Exception
{ {
NodeRef contentNodeRef = createContent(); 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 // Turn the content into a translation with the appropriate structures
NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(contentNodeRef, Locale.CHINESE); NodeRef mlContainerNodeRef = multilingualContentService.makeTranslation(contentNodeRef, Locale.CHINESE);
// Check it // Check it
assertNotNull("Container not created", mlContainerNodeRef); assertNotNull("Container not created", mlContainerNodeRef);
// Check the container child count // Check the container child count
assertEquals("Incorrect number of child nodes", 1, nodeService.getChildAssocs(mlContainerNodeRef).size()); 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 public void testAddTranslationUsingContainer() throws Exception

View File

@@ -149,21 +149,16 @@ public class MultilingualDocumentAspect implements
*/ */
public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef destinationRef, boolean copyToNewNode, Map<NodeRef, NodeRef> copyMap) public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef destinationRef, boolean copyToNewNode, Map<NodeRef, NodeRef> copyMap)
{ {
Map<QName, Serializable> copiedProp = nodeService.getProperties(destinationRef); nodeService.removeProperty(destinationRef, ContentModel.PROP_LOCALE);
copiedProp.remove(ContentModel.PROP_LOCALE);
nodeService.setProperties(destinationRef, copiedProp);
} }
/** /**
* A <b>cm:mlDocument</b> is pivot translation it is a multilingual document (non empty) if it's language matches the language * If the node is multilingual and it is the pivot translation, then deletion is not allowed unless the
* of its <b>cm:mlDocument</b>. And a pivot translation can't be removed if it's not the last translation of the <b>cm:mlContainer</b>.
*
* If a translation is deleted, it's multilingual aspect is lost.
*
* @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
*/ */
public void beforeDeleteNode(NodeRef nodeRef) public void beforeDeleteNode(NodeRef nodeRef)
{ {
// checkRemoveParentMLContainer(nodeRef);
// Remove the aspect
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); nodeService.removeAspect(nodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT);
} }
@@ -178,60 +173,52 @@ public class MultilingualDocumentAspect implements
*/ */
public void beforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) 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); return;
}
// nothing to do if the mlContainer is in a deletion process NodeRef mlContainer = multilingualContentService.getTranslationContainer(nodeRef);
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); // nothing to do if the mlContainer is in a deletion process
Locale nodeRefLocale = (Locale) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCALE); 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;
}
nodeService.removeChild(mlContainer, nodeRef); Locale mlContainerLocale = (Locale) nodeService.getProperty(mlContainer, ContentModel.PROP_LOCALE);
Locale nodeRefLocale = (Locale) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCALE);
// if last translation or if nodeRef is pivot translation nodeService.removeChild(mlContainer, nodeRef);
if(multilingualContentService.getTranslations(mlContainer).size() == 0
|| mlContainerLocale.equals(nodeRefLocale)) // if last translation or if nodeRef is pivot translation
{ if (multilingualContentService.getTranslations(mlContainer).size() == 0
// delete the mlContainer || mlContainerLocale.equals(nodeRefLocale))
nodeService.deleteNode(mlContainer); {
} // delete the mlContainer
nodeService.deleteNode(mlContainer);
} }
} }
/** /**
* After removing the <b>cm:mlDocument</b> aspect : * Removes the document's locale and the <b>cm:mlEmptyTranslation</b> aspect,
* - the node is removed is it's a <b>cm:mlEmptyTranslation</b> * if present.
* - if not, only the locale property is removed
*
* @see org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
*/ */
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) 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))
{ {
nodeService.removeAspect(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<QName, Serializable> props = nodeService.getProperties(nodeRef);
props.remove(ContentModel.PROP_LOCALE);
nodeService.setProperties(nodeRef, props);
}
} }
} }

View File

@@ -119,8 +119,6 @@ public class EmptyTranslationAspectTest extends AbstractMultilingualTestCases {
fileFolderService.getWriter(pivot).putContent(contentString); 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 // 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", assertEquals("The content retourned of the empty translation must be the same that the content of the pivot",
fileFolderService.getReader(pivot).getContentString(), fileFolderService.getReader(pivot).getContentString(),

View File

@@ -82,15 +82,13 @@ public class MultilingualDocumentAspectTest extends AbstractMultilingualTestCase
NodeRef restoredNode = nodeArchiveService.restoreArchivedNode(nodeArchiveService.getArchivedNode(trad3)).getRestoredNodeRef(); NodeRef restoredNode = nodeArchiveService.restoreArchivedNode(nodeArchiveService.getArchivedNode(trad3)).getRestoredNodeRef();
// Ensure that the restored node is restored to it s original space // 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 // 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 // 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 // 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)); assertNull("The restaured node can't keep the locale property", nodeService.getProperty(restoredNode, ContentModel.PROP_LOCALE));
} }
public void testDeletePivot() throws Exception public void testDeletePivot() throws Exception

View File

@@ -30,9 +30,11 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.alfresco.i18n.I18NUtil; import org.alfresco.i18n.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition; 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.MLText;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
@@ -72,6 +74,8 @@ public class MLPropertyInterceptor implements MethodInterceptor
/** Direct access to the NodeService */ /** Direct access to the NodeService */
private NodeService directNodeService; private NodeService directNodeService;
/** Direct access to the ML Content Service */
private MultilingualContentService directMultilingualContentService;
/** Used to access property definitions */ /** Used to access property definitions */
private DictionaryService dictionaryService; private DictionaryService dictionaryService;
@@ -109,6 +113,11 @@ public class MLPropertyInterceptor implements MethodInterceptor
this.directNodeService = bean; this.directNodeService = bean;
} }
public void setDirectMultilingualContentService(MultilingualContentService directMultilingualContentService)
{
this.directMultilingualContentService = directMultilingualContentService;
}
public void setDictionaryService(DictionaryService dictionaryService) public void setDictionaryService(DictionaryService dictionaryService)
{ {
this.dictionaryService = dictionaryService; this.dictionaryService = dictionaryService;
@@ -139,43 +148,26 @@ public class MLPropertyInterceptor implements MethodInterceptor
if (methodName.equals("getProperty")) if (methodName.equals("getProperty"))
{ {
ret = invocation.proceed(); NodeRef nodeRef = (NodeRef) args[0];
// The return value might need to be converted to a String QName propertyQName = (QName) args[1];
if (ret != null && ret instanceof MLText)
{ Serializable value = (Serializable) invocation.proceed();
MLText mlText = (MLText) ret; ret = convertOutboundProperty(contentLocale, nodeRef, propertyQName, value);
ret = mlText.getClosestValue(contentLocale);
// done
if (logger.isDebugEnabled())
{
logger.debug(
"Converted ML text: \n" +
" initial: " + mlText + "\n" +
" converted: " + ret);
}
}
} }
else if (methodName.equals("getProperties")) else if (methodName.equals("getProperties"))
{ {
NodeRef nodeRef = (NodeRef) args[0];
Map<QName, Serializable> properties = (Map<QName, Serializable>) invocation.proceed(); Map<QName, Serializable> properties = (Map<QName, Serializable>) invocation.proceed();
Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(properties.size() * 2); Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(properties.size() * 2);
// Check each return value type // Check each return value type
for (Map.Entry<QName, Serializable> entry : properties.entrySet()) for (Map.Entry<QName, Serializable> entry : properties.entrySet())
{ {
QName key = entry.getKey(); QName propertyQName = entry.getKey();
Serializable value = entry.getValue(); Serializable value = entry.getValue();
if (value != null && value instanceof MLText) Serializable convertedValue = convertOutboundProperty(contentLocale, nodeRef, propertyQName, value);
{ // Add it to the return map
MLText mlText = (MLText) value; convertedProperties.put(propertyQName, convertedValue);
value = mlText.getClosestValue(contentLocale);
// Store the converted value
convertedProperties.put(key, value);
}
else
{
// The value goes straight back in
convertedProperties.put(key, value);
}
} }
ret = convertedProperties; ret = convertedProperties;
// Done // Done
@@ -212,7 +204,6 @@ public class MLPropertyInterceptor implements MethodInterceptor
} }
else if (methodName.equals("setProperty")) else if (methodName.equals("setProperty"))
{ {
//check if the property is of type MLText
NodeRef nodeRef = (NodeRef) args[0]; NodeRef nodeRef = (NodeRef) args[0];
QName propertyQName = (QName) args[1]; QName propertyQName = (QName) args[1];
Serializable inboundValue = (Serializable) args[2]; Serializable inboundValue = (Serializable) args[2];
@@ -232,6 +223,64 @@ public class MLPropertyInterceptor implements MethodInterceptor
return ret; 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 * @param inboundValue The value that must be set

View File

@@ -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<ChildAssociationRef> allChildAssoc = (List<ChildAssociationRef>) 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<ChildAssociationRef> toReturn = new ArrayList();
// the ml containers found in the folder
List<NodeRef> 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<Locale> 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;
}
}

View File

@@ -694,7 +694,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// First get the node to ensure that it exists // First get the node to ensure that it exists
Node node = getNodeNotNull(nodeRef); Node node = getNodeNotNull(nodeRef);
boolean isArchivedNode = false;
boolean requiresDelete = false; boolean requiresDelete = false;
// Invoke policy behaviours // Invoke policy behaviours
@@ -713,7 +712,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// the node has the temporary aspect meaning // the node has the temporary aspect meaning
// it can not be archived // it can not be archived
requiresDelete = true; requiresDelete = true;
isArchivedNode = false;
} }
else else
{ {
@@ -731,17 +729,15 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
{ {
// perform a normal deletion // perform a normal deletion
nodeDaoService.deleteNode(node, true); nodeDaoService.deleteNode(node, true);
isArchivedNode = false; // Invoke policy behaviours
invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, false);
} }
else else
{ {
// archive it // archive it
archiveNode(nodeRef, archiveStoreRef); 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) public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)

View File

@@ -66,7 +66,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
protected NodeService getNodeService() protected NodeService getNodeService()
{ {
return (NodeService) applicationContext.getBean("nodeService"); return (NodeService) applicationContext.getBean("dbNodeService");
} }
@Override @Override

View File

@@ -52,6 +52,15 @@ public interface MultilingualContentService
@Auditable(key = Auditable.Key.ARG_0, parameters = {"localizedNodeRef"}) @Auditable(key = Auditable.Key.ARG_0, parameters = {"localizedNodeRef"})
void renameWithMLExtension(NodeRef localizedNodeRef); void renameWithMLExtension(NodeRef localizedNodeRef);
/**
* Checks whether an existing document is part of a translation group.
*
* @param contentNodeRef An existing <b>cm:content</b>
* @return Returns <tt>true</tt> if the document has a <b>cm:mlContainer</b> parent
*/
@Auditable(key = Auditable.Key.ARG_0, parameters = {"contentNodeRef"})
boolean isTranslation(NodeRef contentNodeRef);
/** /**
* Make an existing document into a translation by adding the <b>cm:mlDocument</b> aspect and * Make an existing document into a translation by adding the <b>cm:mlDocument</b> aspect and
* creating a <b>cm:mlContainer</b> parent. If it is already a translation, then nothing is done. * creating a <b>cm:mlContainer</b> parent. If it is already a translation, then nothing is done.
@@ -132,12 +141,14 @@ public interface MultilingualContentService
List<Locale> getMissingTranslations(NodeRef localizedNodeRef, boolean addThisNodeLocale); List<Locale> getMissingTranslations(NodeRef localizedNodeRef, boolean addThisNodeLocale);
/** /**
* Given any node, this returns the pivot translation. The pivot translation is the translation * Given any node, this returns the pivot translation. All multilingual documents belong to
* that its locale is referenced by the locale of the MLContainer. The translation can't be an * a group linked by a hidden parent node of type <b>cm:mlContainer</b>. The pivot language
* empty translation. * 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 * @param nodeRef a <b>cm:mlDocument</b>
* @return the pivot translation * @return Returns a corresponding <b>cm:mlDocument</b> that matches the locale of
* of the <b>cm:mlContainer</b>.
*/ */
@Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"})
NodeRef getPivotTranslation(NodeRef nodeRef); NodeRef getPivotTranslation(NodeRef nodeRef);

View File

@@ -26,6 +26,7 @@ package org.alfresco.service.cmr.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentData;
@@ -61,6 +62,15 @@ public interface FileInfo
*/ */
public NodeRef getLinkNodeRef(); 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<Locale, FileInfo> getTranslations();
/** /**
* @return Returns the name of the file or folder within the parent folder * @return Returns the name of the file or folder within the parent folder
*/ */