From fbcc541bfd6d329e61354a530599d94c30889083 Mon Sep 17 00:00:00 2001
From: Thomas Mortagne <thomas.mortagne@gmail.com>
Date: Tue, 4 Oct 2016 10:58:29 +0200
Subject: [PATCH] XWIKI-13766: When uninstalling a XAR pages which are part of
 several extensions are sometimes selected XWIKI-13764: XAR extension pages
 index is not properly filled at init

---
 .../XarInstalledExtensionRepository.java      | 34 +++++++++++--
 .../handler/XarExtensionHandlerTest.java      | 35 +++++++++++--
 .../XarInstalledExtensionRepositoryTest.java  | 49 +++++++++++++------
 .../model/reference/DocumentReference.java    | 22 ++++++++-
 4 files changed, 114 insertions(+), 26 deletions(-)

diff --git a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepository.java b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepository.java
index 82c027aaf1a..73ba5ec8c2f 100644
--- a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepository.java
+++ b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepository.java
@@ -24,6 +24,7 @@
 import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -106,6 +107,12 @@ private void pagesUpdated(ExtensionId extensionId, String namespace, boolean add
     {
         XarInstalledExtension installedExtension = (XarInstalledExtension) getInstalledExtension(extensionId);
 
+        pagesUpdated(installedExtension, namespace, add);
+    }
+
+    private void pagesUpdated(XarInstalledExtension installedExtension, String namespace, boolean add)
+        throws UnsupportedNamespaceException
+    {
         if (installedExtension != null) {
             for (XarEntry xarEntry : installedExtension.getXarPackage().getEntries()) {
                 if (namespace != null) {
@@ -165,11 +172,14 @@ void updateCachedXarExtension(ExtensionId extensionId)
         }
     }
 
-    private void addCacheXarExtension(InstalledExtension installedExtension) throws IOException, XarException
+    private XarInstalledExtension addCacheXarExtension(InstalledExtension installedExtension)
+        throws IOException, XarException
     {
         XarInstalledExtension xarExtension = new XarInstalledExtension(installedExtension, this);
 
         addCachedExtension(xarExtension);
+
+        return xarExtension;
     }
 
     protected void removeCachedXarExtension(ExtensionId extensionId)
@@ -186,9 +196,21 @@ private void loadExtensions()
         for (InstalledExtension localExtension : this.installedRepository.getInstalledExtensions()) {
             if (localExtension.getType().equalsIgnoreCase(XarExtensionHandler.TYPE)) {
                 try {
-                    addCacheXarExtension(localExtension);
+                    // Add XAR extension to the cache
+                    XarInstalledExtension xarInstalledExtension = addCacheXarExtension(localExtension);
+
+                    // Add extension pages to the index
+                    if (xarInstalledExtension.getNamespaces() == null) {
+                        pagesUpdated(xarInstalledExtension, null, true);
+                    } else {
+                        for (String namespace : localExtension.getNamespaces()) {
+                            pagesUpdated(xarInstalledExtension, namespace, true);
+                        }
+                    }
                 } catch (Exception e) {
                     this.logger.error("Failed to parse extension [{}]", localExtension.getId(), e);
+
+                    continue;
                 }
             }
         }
@@ -201,8 +223,12 @@ private void loadExtensions()
      */
     public Collection<XarInstalledExtension> getXarInstalledExtensions(DocumentReference reference)
     {
-        Collection<XarInstalledExtension> wikiExtensions = this.documents.get(reference);
-        Collection<XarInstalledExtension> rootExtensions = this.rootDocuments.get(reference);
+        Collection<XarInstalledExtension> wikiExtensions = this.documents
+            .get(reference.getLocale() == null ? new DocumentReference(reference, Locale.ROOT) : reference);
+        Collection<XarInstalledExtension> rootExtensions =
+            this.rootDocuments.get(reference.getLocaleDocumentReference().getLocale() == null
+                ? new LocalDocumentReference(reference.getLocaleDocumentReference(), Locale.ROOT)
+                : reference.getLocaleDocumentReference());
 
         List<XarInstalledExtension> allExtensions = new ArrayList<>();
 
diff --git a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/handler/XarExtensionHandlerTest.java b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/handler/XarExtensionHandlerTest.java
index e922e339389..0c2ec18536c 100644
--- a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/handler/XarExtensionHandlerTest.java
+++ b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/handler/XarExtensionHandlerTest.java
@@ -42,6 +42,7 @@
 import org.xwiki.extension.repository.InstalledExtensionRepository;
 import org.xwiki.extension.test.MockitoRepositoryUtilsRule;
 import org.xwiki.extension.xar.internal.repository.XarInstalledExtension;
+import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
 import org.xwiki.job.Job;
 import org.xwiki.job.JobExecutor;
 import org.xwiki.logging.LogLevel;
@@ -67,6 +68,7 @@
 import com.xpn.xwiki.test.MockitoOldcoreRule;
 import com.xpn.xwiki.util.XWikiStubContextProvider;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -78,14 +80,16 @@
 @AllComponents
 public class XarExtensionHandlerTest
 {
-    private MockitoComponentManagerRule componentManager = new MockitoComponentManagerRule();
+    private MockitoOldcoreRule oldcore = new MockitoOldcoreRule();
 
-    private MockitoOldcoreRule oldcore = new MockitoOldcoreRule(this.componentManager);
+    private MockitoComponentManagerRule componentManager = this.oldcore.getMocker();
 
     @Rule
     public MockitoRepositoryUtilsRule repositoryUtil =
         new MockitoRepositoryUtilsRule(this.componentManager, this.oldcore);
 
+    private XarInstalledExtensionRepository installedExtensionRepository;
+
     private ExtensionId localXarExtensiontId1;
 
     private ExtensionId localXarExtensiontId2;
@@ -143,6 +147,9 @@ public void setUp() throws Exception
         // Get rid of wiki macro listener
         this.componentManager.<ObservationManager>getInstance(ObservationManager.class)
             .removeListener("RegisterMacrosOnImportListener");
+
+        this.installedExtensionRepository =
+            this.componentManager.getInstance(InstalledExtensionRepository.class, "xar");
     }
 
     private void mockHasAdminRight(boolean right) throws XWikiException
@@ -241,7 +248,7 @@ public void testInstallOnWiki() throws Throwable
 
         // install
 
-        install(this.localXarExtensiontId1, "wiki", this.contextUser);
+        XarInstalledExtension xarInstalledExtension = install(this.localXarExtensiontId1, "wiki", this.contextUser);
 
         verifyHasAdminRight(2);
 
@@ -295,6 +302,15 @@ public void testInstallOnWiki() throws Throwable
 
         Assert.assertFalse("Document wiki:space1.page1 has not been saved in the database", page1.isNew());
 
+        assertEquals(Arrays.asList(xarInstalledExtension),
+            this.installedExtensionRepository.getXarInstalledExtensions(page1.getDocumentReference()));
+        assertEquals(Arrays.asList(xarInstalledExtension),
+            this.installedExtensionRepository.getXarInstalledExtensions(page1.getDocumentReferenceWithLocale()));
+        assertEquals(0, this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("wiki", "space1", "page1", Locale.ENGLISH)).size());
+        assertEquals(0, this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("otherwiki", "space1", "page1")).size());
+
         // translated.translated
         DocumentReference translatedReference = new DocumentReference("wiki", "translated", "translated");
         XWikiDocument defaultTranslated =
@@ -311,6 +327,9 @@ public void testInstallOnWiki() throws Throwable
         Assert.assertEquals("Wrong content author", this.contextUser, defaultTranslated.getContentAuthorReference());
         Assert.assertEquals("Wrong version", "1.1", defaultTranslated.getVersion());
 
+        assertEquals(Arrays.asList(xarInstalledExtension), this.installedExtensionRepository
+            .getXarInstalledExtensions(defaultTranslated.getDocumentReferenceWithLocale()));
+
         // translated.translated.tr
         XWikiDocument translated =
             this.oldcore.getDocuments().get(new DocumentReference(translatedReference, new Locale("tr")));
@@ -326,6 +345,9 @@ public void testInstallOnWiki() throws Throwable
         Assert.assertEquals("Wrong content author", this.contextUser, translated.getContentAuthorReference());
         Assert.assertEquals("Wrong version", "1.1", translated.getVersion());
 
+        assertEquals(Arrays.asList(xarInstalledExtension),
+            this.installedExtensionRepository.getXarInstalledExtensions(translated.getDocumentReferenceWithLocale()));
+
         // translated.translated.fr
         XWikiDocument translated2 =
             this.oldcore.getDocuments().get(new DocumentReference(translatedReference, new Locale("fr")));
@@ -341,6 +363,9 @@ public void testInstallOnWiki() throws Throwable
         Assert.assertEquals("Wrong content author", this.contextUser, translated2.getContentAuthorReference());
         Assert.assertEquals("Wrong version", "1.1", translated2.getVersion());
 
+        assertEquals(Arrays.asList(xarInstalledExtension),
+            this.installedExtensionRepository.getXarInstalledExtensions(translated2.getDocumentReferenceWithLocale()));
+
         // space.hiddenpage
 
         XWikiDocument hiddenpage = this.oldcore.getSpyXWiki()
@@ -796,11 +821,13 @@ public void testUninstallFromWiki() throws Throwable
 
         // validate
 
+        // space.page belong to several extensions
         XWikiDocument page =
             this.oldcore.getSpyXWiki().getDocument(new DocumentReference("wiki", "space", "page"), getXWikiContext());
 
-        Assert.assertTrue("Document wiki.space.page has not been removed from the database", page.isNew());
+        Assert.assertFalse("Document wiki.space.page has been removed from the database", page.isNew());
 
+        // space1.page1 only belong to the uninstalled extension
         XWikiDocument page1 =
             this.oldcore.getSpyXWiki().getDocument(new DocumentReference("wiki", "space1", "page1"), getXWikiContext());
 
diff --git a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepositoryTest.java b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepositoryTest.java
index b9e98b69161..d0c0d7c0ad2 100644
--- a/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepositoryTest.java
+++ b/xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/test/java/org/xwiki/extension/xar/internal/repository/XarInstalledExtensionRepositoryTest.java
@@ -19,7 +19,9 @@
  */
 package org.xwiki.extension.xar.internal.repository;
 
-import org.junit.Assert;
+import java.util.Arrays;
+import java.util.Locale;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -28,10 +30,14 @@
 import org.xwiki.extension.repository.InstalledExtensionRepository;
 import org.xwiki.extension.repository.search.SearchException;
 import org.xwiki.extension.test.MockitoRepositoryUtilsRule;
-import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
+import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.test.annotation.AllComponents;
 import org.xwiki.test.mockito.MockitoComponentManagerRule;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
 @AllComponents
 public class XarInstalledExtensionRepositoryTest
 {
@@ -53,22 +59,33 @@ public void setUp() throws Exception
     @Test
     public void testInit() throws ResolveException, SearchException
     {
-        Assert.assertTrue(this.installedExtensionRepository.countExtensions() == 1);
+        assertEquals(1, this.installedExtensionRepository.countExtensions());
+
+        XarInstalledExtension xarInstalledExtension =
+            this.installedExtensionRepository.resolve(new ExtensionId("xarinstalledextension", "1.0"));
+        assertNotNull(xarInstalledExtension);
+
+        assertNotNull(
+            this.installedExtensionRepository.getInstalledExtension(new ExtensionId("xarinstalledextension", "1.0")));
+        assertNotNull(this.installedExtensionRepository.getInstalledExtension("xarinstalledextension", null));
+        assertNull(this.installedExtensionRepository.getInstalledExtension("notexisting", null));
 
-        Assert
-        .assertNotNull(this.installedExtensionRepository.resolve(new ExtensionId("xarinstalledextension", "1.0")));
-        
-        Assert.assertNotNull(this.installedExtensionRepository.getInstalledExtension(new ExtensionId(
-            "xarinstalledextension", "1.0")));
-        Assert.assertNotNull(this.installedExtensionRepository.getInstalledExtension("xarinstalledextension", null));
-        Assert.assertNull(this.installedExtensionRepository.getInstalledExtension("notexisting", null));
+        assertEquals(1, this.installedExtensionRepository.getInstalledExtensions().size());
+        assertEquals(1, this.installedExtensionRepository.getInstalledExtensions(null).size());
 
-        Assert.assertEquals(1, this.installedExtensionRepository.getInstalledExtensions().size());
-        Assert.assertEquals(1, this.installedExtensionRepository.getInstalledExtensions(null).size());
+        assertEquals(1, this.installedExtensionRepository.search("xarinstalledextension", 0, -1).getSize());
+        assertEquals(1, this.installedExtensionRepository.search(null, 0, -1).getSize());
+        assertEquals(1, this.installedExtensionRepository
+            .searchInstalledExtensions("xarinstalledextension", null, 0, -1).getSize());
+        assertEquals(1, this.installedExtensionRepository.searchInstalledExtensions(null, null, 0, -1).getSize());
 
-        Assert.assertEquals(1, this.installedExtensionRepository.search("xarinstalledextension", 0, -1).getSize());
-        Assert.assertEquals(1, this.installedExtensionRepository.search(null, 0, -1).getSize());
-        Assert.assertEquals(1, this.installedExtensionRepository.searchInstalledExtensions("xarinstalledextension", null, 0, -1).getSize());
-        Assert.assertEquals(1, this.installedExtensionRepository.searchInstalledExtensions(null, null, 0, -1).getSize());
+        assertEquals(Arrays.asList(xarInstalledExtension), this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("xwiki", "space", "page")));
+        assertEquals(Arrays.asList(xarInstalledExtension), this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("wiki2", "space", "page")));
+        assertEquals(Arrays.asList(xarInstalledExtension), this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("xwiki", "space", "page", Locale.ROOT)));
+        assertEquals(0, this.installedExtensionRepository
+            .getXarInstalledExtensions(new DocumentReference("xwiki", "space", "page", Locale.ENGLISH)).size());
     }
 }
diff --git a/xwiki-platform-core/xwiki-platform-model/src/main/java/org/xwiki/model/reference/DocumentReference.java b/xwiki-platform-core/xwiki-platform-model/src/main/java/org/xwiki/model/reference/DocumentReference.java
index 58939d20c03..84557f8b4ef 100644
--- a/xwiki-platform-core/xwiki-platform-model/src/main/java/org/xwiki/model/reference/DocumentReference.java
+++ b/xwiki-platform-core/xwiki-platform-model/src/main/java/org/xwiki/model/reference/DocumentReference.java
@@ -52,6 +52,11 @@ public class DocumentReference extends EntityReference
      */
     static final String LOCALE = "LOCALE";
 
+    /**
+     * Cache the {@link LocalDocumentReference} corresponding to this {@link DocumentReference}.
+     */
+    private LocalDocumentReference localDocumentReference;
+
     /**
      * Special constructor that transforms a generic entity reference into a {@link DocumentReference}. It checks the
      * validity of the passed reference (ie correct type and correct parent).
@@ -126,8 +131,8 @@ public DocumentReference(String wikiName, String spaceName, String pageName, Loc
      */
     public DocumentReference(String wikiName, String spaceName, String pageName, String language)
     {
-        this(pageName, new SpaceReference(spaceName, new WikiReference(wikiName)), (language == null) ? null
-            : new Locale(language));
+        this(pageName, new SpaceReference(spaceName, new WikiReference(wikiName)),
+            (language == null) ? null : new Locale(language));
     }
 
     /**
@@ -320,6 +325,19 @@ public DocumentReference replaceParent(EntityReference oldParent, EntityReferenc
         return new DocumentReference(this, oldParent, newParent);
     }
 
+    /**
+     * @return the {@link LocalDocumentReference} corresponding to this {@link DocumentReference}
+     * @since 8.3
+     */
+    public LocalDocumentReference getLocaleDocumentReference()
+    {
+        if (this.localDocumentReference == null) {
+            this.localDocumentReference = new LocalDocumentReference(this);
+        }
+
+        return this.localDocumentReference;
+    }
+
     @Override
     public String toString()
     {
-- 
GitLab