diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 62fc9112d0..d126e056c6 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -466,3 +466,6 @@ patch.db-V5.0-upgrade-to-activiti-5.16.2.result=Activiti tables updated patch.solrFacets.root.description=Creates the solr facets root folder in the Data Dictionary +patch.replaceForbiddenTagCharSequencesPatch.description=This patches Alfresco tags to remove broken char sequences from tag names. See MNT-11871 +patch.replaceForbiddenTagCharSequencesPatch.result=Tags have been patched successfully + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 3c3f12a793..d4f94d82ca 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -3391,4 +3391,26 @@ + + + patch.replaceForbiddenTagCharSequencesPatch + + + patch.replaceForbiddenTagCharSequencesPatch.description + + + 0 + + + 8005 + + + 8006 + + + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 7e363998c7..612754dee5 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -23,4 +23,4 @@ version.build=r@scm-revision@-b@build-number@ # Schema number -version.schema=8005 +version.schema=8006 diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ReplaceForbiddenTagCharSequencesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ReplaceForbiddenTagCharSequencesPatch.java new file mode 100644 index 0000000000..aaa4835d9c --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ReplaceForbiddenTagCharSequencesPatch.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.admin.patch.impl; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.tagging.TagScope; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * This patches Alfresco tags to remove broken char sequences from tag names + * + * @see MNT-11871 + * @author sergey.shcherbovich + */ +public class ReplaceForbiddenTagCharSequencesPatch extends AbstractPatch +{ + private String REPLACER = "_"; + private String[] BAD_TAG_SEQUENCES = new String[] {"\n", "|"}; + + private static Log logger = LogFactory.getLog(ReplaceForbiddenTagCharSequencesPatch.class); + + private static String SUCCESS_MESSAGE = "patch.replaceForbiddenTagCharSequencesPatch.result"; + + private TaggingService taggingService; + + @Override + protected String applyInternal() throws Exception + { + Set tagScopesToRefresh = new HashSet<>(); + + for (StoreRef storeRef : nodeService.getStores()) + { + for (String bad : BAD_TAG_SEQUENCES) + { + List brokenTags = taggingService.getTags(storeRef, bad); + + for (String tag : brokenTags) + { + List taggetNodes = taggingService.findTaggedNodes(storeRef, tag); + + String newTag = findName(storeRef, bad, tag); + + if (logger.isDebugEnabled()) + { + logger.debug("Broken tag in " + storeRef + " is " + tag + ", rename to " + newTag); + } + + taggingService.changeTag(storeRef, tag, newTag); + + for (NodeRef nodeRef : taggetNodes) + { + tagScopesToRefresh.addAll(taggingService.findAllTagScopes(nodeRef)); + } + } + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("Scopes to refresh : " + tagScopesToRefresh.size()); + } + + for (TagScope tagScope : tagScopesToRefresh) + { + taggingService.refreshTagScope(tagScope.getNodeRef(), false); + } + + return I18NUtil.getMessage(SUCCESS_MESSAGE); + } + + private String findName(StoreRef storeRef, String badSequence, String tag) + { + String newTag = tag.replace(badSequence, REPLACER); + + if (taggingService.getTagNodeRef(storeRef, newTag) != null) + { + return findName(storeRef, badSequence, tag.replace(badSequence, badSequence + REPLACER)); + } + + return newTag; + } + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } +} diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java index 43d2cd9bc5..fe910eb224 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java @@ -25,13 +25,16 @@ import java.io.InputStreamReader; import java.io.Serializable; import java.text.Collator; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -73,6 +76,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO9075; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -109,6 +113,10 @@ public class TaggingServiceImpl implements TaggingService, /** Tag Details Delimiter */ private static final String TAG_DETAILS_DELIMITER = "|"; + /** Next tag delimiter */ + private static final String NEXT_TAG_DELIMITER = "\n"; + + private static Set FORBIDDEN_TAGS_SEQUENCES = new HashSet(Arrays.asList(new String[] {NEXT_TAG_DELIMITER, TAG_DETAILS_DELIMITER})); /** Policy behaviour */ private JavaBehaviour updateTagBehaviour; @@ -720,6 +728,14 @@ public class TaggingServiceImpl implements TaggingService, */ private NodeRef getTagNodeRef(StoreRef storeRef, String tag, boolean create) { + for (String forbiddenSequence : FORBIDDEN_TAGS_SEQUENCES) + { + if (create && tag.contains(forbiddenSequence)) + { + throw new IllegalArgumentException("Tag name must not contain " + StringEscapeUtils.escapeJava(forbiddenSequence) + " char sequence"); + } + } + NodeRef tagNodeRef = null; Collection results = this.categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, tag, create); if (!results.isEmpty()) @@ -1314,7 +1330,7 @@ public class TaggingServiceImpl implements TaggingService, { if (bFirst == false) { - result.append("\n"); + result.append(NEXT_TAG_DELIMITER); } else { diff --git a/source/test-java/org/alfresco/repo/tagging/TaggingServiceImplTest.java b/source/test-java/org/alfresco/repo/tagging/TaggingServiceImplTest.java index af063d7394..2d5f5379e3 100644 --- a/source/test-java/org/alfresco/repo/tagging/TaggingServiceImplTest.java +++ b/source/test-java/org/alfresco/repo/tagging/TaggingServiceImplTest.java @@ -124,6 +124,9 @@ public class TaggingServiceImplTest extends TestCase private static final String TAG_5 = "tag five"; private static final String TAG_I18N = "àâæçéèêëîïôœùûüÿñ"; + private static final String BAD_TAG = "bad \n tag"; + private static final String BAD_TAG2 = "Broken|2"; + private static final String UPPER_TAG = "House"; private static final String LOWER_TAG = "house"; @@ -2159,4 +2162,55 @@ public class TaggingServiceImplTest extends TestCase assertEquals(tags.get(1).getCount(), 20); assertEquals(tags.get(2).getCount(), 1); } + + /* Test adding tags containing \n and | chars. Test all ways to create tag (e.g. createTag, addTag, setTags) */ + public void testBadTags() + { + testTag(BAD_TAG); + testTag(BAD_TAG2); + } + + private void testTag(final String tag) + { + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + @Override + public Void execute() throws Throwable + { + try + { + taggingService.createTag(storeRef, tag); + fail(); + } + catch(IllegalArgumentException iae) + { + // + } + + try + { + taggingService.addTag(document, tag); + fail(); + } + catch(IllegalArgumentException iae) + { + // + } + + try + { + List setTags = new ArrayList(2); + setTags.add(tag); + taggingService.setTags(document, setTags); + + fail(); + } + catch(IllegalArgumentException iae) + { + // + } + + return null; + } + }); + } }