From 073532d758f7bf0c525c5fbad8ef90aed14f1b5a Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 25 Sep 2025 11:40:35 +0200 Subject: [PATCH 01/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Start modifying the REST API to retrieve the version information from different page version when it's enabled * WIP: those changes depends on new APIs that are not implemented yet --- .../internal/XWikiRepositoryModel.java | 5 + .../AbstractExtensionRESTResource.java | 193 +++++++++--------- .../ExtensionVersionsRESTResource.java | 27 +-- .../resources/ExtensionsRESTResource.java | 2 +- 4 files changed, 101 insertions(+), 126 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java index 4cb012795261..3eeed847aea7 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java @@ -198,6 +198,11 @@ public class XWikiRepositoryModel */ public static final String PROP_VERSION_FEATURES = "features"; + /** + * @since 17.9.0RC1 + */ + public static final String PROP_VERSION_INDEX = "index"; + /** * @since 7.3M1 */ diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java index 802313f1e459..828278fc1c56 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java @@ -93,38 +93,36 @@ */ public abstract class AbstractExtensionRESTResource extends XWikiResource implements Initializable { - public static final String[] EPROPERTIES_SUMMARY = new String[] {XWikiRepositoryModel.PROP_EXTENSION_ID, - XWikiRepositoryModel.PROP_EXTENSION_TYPE, XWikiRepositoryModel.PROP_EXTENSION_NAME}; - protected static final String DEFAULT_BOOST; protected static final String DEFAULT_FL; - protected static Map EPROPERTIES_INDEX = new HashMap(); + protected static final Map EPROPERTIES_INDEX = Map.of( + XWikiRepositoryModel.PROP_EXTENSION_ID, 0, + XWikiRepositoryModel.PROP_EXTENSION_NAME, 1, + XWikiRepositoryModel.PROP_EXTENSION_TYPE, 2, + XWikiRepositoryModel.PROP_VERSION_VERSION, 3 + ); + + // Note that the order of the retrieved information is stable and documented by EPROPERTIES_INDEX, + // therefore it shouldn't be changed or it would break some of the APIs relying on it. + protected static final String SELECT_EXTENSIONSUMMARY = String.format("doc.name, doc.space, " + + "extension.%s, extension.%s, extension.%s", + XWikiRepositoryModel.PROP_EXTENSION_ID, + XWikiRepositoryModel.PROP_EXTENSION_NAME, + XWikiRepositoryModel.PROP_EXTENSION_TYPE); + + protected static final String SELECT_EXTENSION_VERSION = String.format("doc.name, doc.space, " + + "extensionVersion.%s, extensionVersion.%s, extensionVersion.%s, " + + "extensionVersion.%s, extensionVersion.%s", + XWikiRepositoryModel.PROP_EXTENSION_ID, + XWikiRepositoryModel.PROP_EXTENSION_NAME, + XWikiRepositoryModel.PROP_EXTENSION_TYPE, + XWikiRepositoryModel.PROP_VERSION_VERSION, + XWikiRepositoryModel.PROP_VERSION_INDEX); - protected static String SELECT_EXTENSIONSUMMARY; static { - StringBuilder pattern = new StringBuilder(); - - int j = 0; - - pattern.append("doc.name"); - EPROPERTIES_INDEX.put("doc.name", j++); - pattern.append(", "); - pattern.append("doc.space"); - EPROPERTIES_INDEX.put("doc.space", j++); - - // Extension summary - for (int i = 0; i < EPROPERTIES_SUMMARY.length; ++i, ++j) { - String value = EPROPERTIES_SUMMARY[i]; - pattern.append(", extension."); - pattern.append(value); - EPROPERTIES_INDEX.put(value, j); - } - - SELECT_EXTENSIONSUMMARY = pattern.toString(); - // Solr StringBuilder boostBuilder = new StringBuilder(); @@ -132,7 +130,7 @@ public abstract class AbstractExtensionRESTResource extends XWikiResource implem for (SolrField field : XWikiRepositoryModel.SOLR_FIELDS.values()) { // Boost if (field.boostValue != null) { - if (boostBuilder.length() > 0) { + if (!boostBuilder.isEmpty()) { boostBuilder.append(' '); } boostBuilder.append(field.boostName); @@ -142,7 +140,7 @@ public abstract class AbstractExtensionRESTResource extends XWikiResource implem // Fields list if (field.name != null) { - if (flBuilder.length() > 0) { + if (!flBuilder.isEmpty()) { flBuilder.append(','); } flBuilder.append(field.name); @@ -203,7 +201,7 @@ protected Query createExtensionsCountQuery(String from, String where) throws Que String select = "count(extension." + XWikiRepositoryModel.PROP_EXTENSION_ID + ")"; - return createExtensionsQuery(select, from, where, 0, -1, false); + return createExtensionsQuery(select, from, where, 0, -1, false, false); } protected long getExtensionsCountResult(Query query) throws QueryException @@ -211,31 +209,37 @@ protected long getExtensionsCountResult(Query query) throws QueryException return ((Number) query.execute().get(0)).intValue(); } - protected Query createExtensionsSummariesQuery(String from, String where, int offset, int number, boolean versions) - throws QueryException + protected Query createExtensionsSummariesQuery(String from, String where, int offset, int number, + boolean versions, boolean pageVersion) throws QueryException { String select = SELECT_EXTENSIONSUMMARY; - return createExtensionsQuery(select, from, where, offset, number, versions); + if (versions && pageVersion) { + select = SELECT_EXTENSION_VERSION; + } + + return createExtensionsQuery(select, from, where, offset, number, versions, pageVersion); } private Query createExtensionsQuery(String select, String from, String where, int offset, int number, - boolean versions) throws QueryException + boolean versions, boolean pageVersion) throws QueryException { // select StringBuilder queryStr = new StringBuilder("select "); queryStr.append(select); - if (versions) { + if (versions && !pageVersion) { queryStr.append(", extensionVersion." + XWikiRepositoryModel.PROP_VERSION_VERSION + ""); } - // from - - queryStr - .append(" from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension"); + queryStr.append(" from Document doc"); + // from + if (!pageVersion) { + queryStr + .append(", doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension"); + } if (versions) { queryStr .append(", doc.object(" + XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME + ") as extensionVersion"); @@ -256,6 +260,9 @@ private Query createExtensionsQuery(String select, String from, String where, in queryStr.append(" and "); } queryStr.append("extension." + XWikiRepositoryModel.PROP_EXTENSION_VALIDEXTENSION + " = 1"); + if (versions && pageVersion) { + queryStr.append(" order by extensionVersion." + XWikiRepositoryModel.PROP_VERSION_INDEX); + } Query query = this.queryManager.createQuery(queryStr.toString(), Query.XWQL); @@ -317,6 +324,7 @@ protected void addLicense(AbstractExtension extension, String licenseName) protected E createExtension(XWikiDocument extensionDocument, String version) { BaseObject extensionObject = getExtensionObject(extensionDocument); + XWikiDocument versionDocument = this.repositoryManager.getExtensionVersionDocument(extensionDocument, version); DocumentReference extensionDocumentReference = extensionDocument.getDocumentReference(); if (extensionObject == null) { @@ -329,7 +337,7 @@ protected E createExtension(XWikiDocument extensio if (version == null) { extension = this.extensionObjectFactory.createExtension(); extensionVersion = null; - extensionVersionObject = null; + extensionVersionObject = extensionObject; } else { extensionVersionObject = repositoryManager.getExtensionVersionObject(extensionDocument, version); @@ -339,29 +347,26 @@ protected E createExtension(XWikiDocument extensio extensionVersion = this.extensionObjectFactory.createExtensionVersion(); extension = extensionVersion; - extensionVersion.setVersion((String) this.extensionStore.getValue(extensionVersionObject, + extensionVersion.setVersion(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION)); } - extension.setId((String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID)); - extension.setType(StringUtils.stripToNull( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); + extension.setId(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_ID)); + extension.setType(StringUtils.stripToNull(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); extension.setRating(getExtensionRating(extensionDocumentReference)); - extension.setSummary( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); - extension.setDescription( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION)); + extension.setSummary(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); + extension.setDescription(this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION)); extension - .setName((String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_NAME)); + .setName(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_NAME)); extension.setCategory( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_CATEGORY)); + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_CATEGORY)); extension.setWebsite(StringUtils.defaultIfEmpty( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE), + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE), extensionDocument.getExternalURL("view", getXWikiContext()))); // Recommended - extension.setRecommended(this.extensionStore.getBooleanValue(extensionVersionObject, + extension.setRecommended(this.extensionStore.getBooleanValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_RECOMMENDED, false)); // Support plans @@ -370,18 +375,18 @@ protected E createExtension(XWikiDocument extensio // SCM ExtensionScm scm = new ExtensionScm(); - scm.setUrl((String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMURL)); + scm.setUrl(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMURL)); scm.setConnection(toScmConnection( - (String) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMCONNECTION))); - scm.setDeveloperConnection(toScmConnection((String) this.extensionStore.getValue(extensionObject, + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMCONNECTION))); + scm.setDeveloperConnection(toScmConnection(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMDEVCONNECTION))); extension.setScm(scm); // Issue Management ExtensionIssueManagement issueManagement = new ExtensionIssueManagement(); - issueManagement.setSystem((String) this.extensionStore.getValue(extensionObject, + issueManagement.setSystem(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_SYSTEM)); - issueManagement.setUrl((String) this.extensionStore.getValue(extensionObject, + issueManagement.setUrl(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_URL)); if (StringUtils.isNotEmpty(issueManagement.getSystem()) || StringUtils.isNotEmpty(issueManagement.getUrl())) { extension.setIssueManagement(issueManagement); @@ -389,36 +394,20 @@ protected E createExtension(XWikiDocument extensio // Authors addExtensionAuthors(extension, - this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS)); + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS)); // Features - List features; - if (extensionVersionObject != null) { - features = (List) this.extensionStore.getValue(extensionVersionObject, + List features = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_FEATURES); - } else { - features = (List) this.extensionStore.getValue(extensionObject, - XWikiRepositoryModel.PROP_EXTENSION_FEATURES); - } - if (features != null && !features.isEmpty()) { - for (String feature : features) { - org.xwiki.extension.ExtensionId extensionId = ExtensionIdConverter.toExtensionId(feature, null); - ExtensionId extensionFeature = this.extensionObjectFactory.createExtensionId(); - extensionFeature.setId(extensionId.getId()); - if (extensionId.getVersion() != null) { - extensionFeature.setVersion(extensionId.getVersion().getValue()); - } - extension.getExtensionFeatures().add(extensionFeature); - extension.getFeatures().add(extensionFeature.getId()); - } - } + + extractFeatureInformation(features, extension); // License addLicense(extension, this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_LICENSENAME)); // Allowed namespaces - List namespaces = (List) this.extensionStore.getValue(extensionObject, + List namespaces = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES); Integer namespacesEmpty = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES_EMPTY, 0); @@ -429,35 +418,32 @@ protected E createExtension(XWikiDocument extensio } // Properties - addProperties(extension, (List) this.extensionStore.getValue(extensionObject, + addProperties(extension, this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_PROPERTIES)); if (extensionVersion != null) { - // Repositories - if (extensionVersionObject != null) { - List repositories = (List) this.extensionStore.getValue(extensionVersionObject, + List repositories = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES); - extensionVersion.withRepositories(toExtensionRepositories(repositories)); - } + extensionVersion.withRepositories(toExtensionRepositories(repositories)); // Dependencies List dependencies = - extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); + versionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); if (dependencies != null) { for (BaseObject dependencyObject : dependencies) { if (dependencyObject != null) { if (Strings.CS.equals(this.extensionStore.getValue(dependencyObject, - XWikiRepositoryModel.PROP_DEPENDENCY_EXTENSIONVERSION, (String) null), version)) { + XWikiRepositoryModel.PROP_DEPENDENCY_EXTENSIONVERSION, null), version)) { ExtensionDependency dependency = extensionObjectFactory.createExtensionDependency(); - dependency.setId((String) this.extensionStore.getValue(dependencyObject, + dependency.setId(this.extensionStore.getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_ID)); - dependency.setConstraint((String) this.extensionStore.getValue(dependencyObject, + dependency.setConstraint(this.extensionStore.getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_CONSTRAINT)); dependency.setOptional(this.extensionStore.getBooleanValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_OPTIONAL, false)); - List repositories = (List) this.extensionStore.getValue(dependencyObject, + List dependencyRepositories = this.extensionStore.getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_REPOSITORIES); - dependency.withRepositories(toExtensionRepositories(repositories)); + dependency.withRepositories(toExtensionRepositories(dependencyRepositories)); extensionVersion.getDependencies().add(dependency); } @@ -469,6 +455,22 @@ protected E createExtension(XWikiDocument extensio return (E) extension; } + private void extractFeatureInformation(Collection features, AbstractExtension extension) + { + if (features != null && !features.isEmpty()) { + for (String feature : features) { + org.xwiki.extension.ExtensionId extensionId = ExtensionIdConverter.toExtensionId(feature, null); + ExtensionId extensionFeature = this.extensionObjectFactory.createExtensionId(); + extensionFeature.setId(extensionId.getId()); + if (extensionId.getVersion() != null) { + extensionFeature.setVersion(extensionId.getVersion().getValue()); + } + extension.getExtensionFeatures().add(extensionFeature); + extension.getFeatures().add(extensionFeature.getId()); + } + } + } + protected ExtensionScmConnection toScmConnection(String connectionString) { if (connectionString != null) { @@ -675,18 +677,7 @@ protected ExtensionVersion createExtensionVersionFromSolrDocument(SolrDocument d // Features Collection features = getSolrValues(document, Extension.FIELD_FEATURES); - if (features != null && !features.isEmpty()) { - for (String feature : features) { - org.xwiki.extension.ExtensionId extensionId = ExtensionIdConverter.toExtensionId(feature, null); - ExtensionId extensionFeature = this.extensionObjectFactory.createExtensionId(); - extensionFeature.setId(extensionId.getId()); - if (extensionId.getVersion() != null) { - extensionFeature.setVersion(extensionId.getVersion().getValue()); - } - extension.getExtensionFeatures().add(extensionFeature); - extension.getFeatures().add(extensionFeature.getId()); - } - } + extractFeatureInformation(features, extension); // License addLicense(extension, getSolrValue(document, Extension.FIELD_LICENSE, true)); @@ -764,7 +755,7 @@ protected void getExtensionSummaries(List extens protected ExtensionSummary createExtensionSummaryFromQueryResult(Object[] entry) { ExtensionSummary extension; - int versionIndex = EPROPERTIES_INDEX.get(EPROPERTIES_SUMMARY[EPROPERTIES_SUMMARY.length - 1]) + 1; + int versionIndex = EPROPERTIES_INDEX.get(XWikiRepositoryModel.PROP_VERSION_VERSION); if (entry.length == versionIndex) { // It's a extension summary without version extension = this.extensionObjectFactory.createExtensionSummary(); diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java index ede02ee92eb5..17fa971f1500 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java @@ -62,7 +62,9 @@ public ExtensionVersions getExtensionVersions(@PathParam("extensionId") String e @QueryParam(Resources.QPARAM_LIST_NUMBER) @DefaultValue("-1") int number, @QueryParam(Resources.QPARAM_VERSIONS_RANGES) String ranges) throws QueryException, InvalidVersionRangeException { - Query query = createExtensionsSummariesQuery(null, "extension.id = :extensionId", 0, -1, true); + boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionId); + Query query = + createExtensionsSummariesQuery(null, "extensionVersion.id = :extensionId", 0, -1, true, versionPageEnabled); query.bindValue("extensionId", extensionId); @@ -86,29 +88,6 @@ public ExtensionVersions getExtensionVersions(@PathParam("extensionId") String e } } - // Order by version - final Map versionCache = new HashMap<>(); - Collections.sort(extensions.getExtensionVersionSummaries(), new Comparator() - { - @Override - public int compare(ExtensionVersionSummary o1, ExtensionVersionSummary o2) - { - return toVersion(o1.getVersion()).compareTo(toVersion(o2.getVersion())); - } - - private Version toVersion(String versionString) - { - Version version = versionCache.get(versionString); - - if (version == null) { - version = extensionFactory.getVersion(versionString); - versionCache.put(versionString, version); - } - - return version; - } - }); - extensions.setTotalHits(extensions.getExtensionVersionSummaries().size()); extensions.setOffset(offset); diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionsRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionsRESTResource.java index 9649bce608fa..85faf98cadfb 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionsRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionsRESTResource.java @@ -59,7 +59,7 @@ public Extensions getExtensions(@QueryParam(Resources.QPARAM_LIST_START) @Defaul extensions.setOffset(offset); - Query query = createExtensionsSummariesQuery(null, null, offset, number, false); + Query query = createExtensionsSummariesQuery(null, null, offset, number, false, false); getExtensionSummaries(extensions.getExtensionSummaries(), query); From bc90f70d603935f2f429222d2ec6ea842b95739b Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Thu, 25 Sep 2025 14:09:16 +0300 Subject: [PATCH 02/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * add concept of separated page mode * start migrating import code to take the new mode into account --- .../repository/internal/ExtensionStore.java | 237 +++++++++- .../internal/RepositoryManager.java | 436 ++++++------------ .../internal/XWikiRepositoryModel.java | 5 + 3 files changed, 388 insertions(+), 290 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 3c08accd77cb..9599f268d7c5 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -22,28 +22,56 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; +import org.xwiki.cache.Cache; +import org.xwiki.cache.CacheException; +import org.xwiki.cache.CacheManager; +import org.xwiki.cache.config.CacheConfiguration; +import org.xwiki.cache.eviction.LRUEvictionConfiguration; import org.xwiki.component.annotation.Component; +import org.xwiki.component.manager.ComponentLifecycleException; +import org.xwiki.component.phase.Disposable; +import org.xwiki.component.phase.Initializable; +import org.xwiki.component.phase.InitializationException; import org.xwiki.extension.DefaultExtensionSupportPlan; import org.xwiki.extension.DefaultExtensionSupportPlans; import org.xwiki.extension.DefaultExtensionSupporter; +import org.xwiki.extension.Extension; import org.xwiki.extension.ExtensionSupportPlan; import org.xwiki.extension.ExtensionSupportPlans; import org.xwiki.extension.ExtensionSupporter; +import org.xwiki.extension.version.Version; +import org.xwiki.extension.version.internal.DefaultVersion; +import org.xwiki.model.EntityType; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReference; +import org.xwiki.model.reference.PageReference; +import org.xwiki.observation.EventListener; +import org.xwiki.observation.ObservationManager; +import org.xwiki.observation.event.Event; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryManager; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.internal.event.XObjectPropertyAddedEvent; +import com.xpn.xwiki.internal.event.XObjectPropertyDeletedEvent; +import com.xpn.xwiki.internal.event.XObjectPropertyUpdatedEvent; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.BaseProperty; import com.xpn.xwiki.objects.NumberProperty; @@ -56,14 +84,94 @@ */ @Component(roles = ExtensionStore.class) @Singleton -public class ExtensionStore +public class ExtensionStore implements Initializable, Disposable { + /** + * The reference to match property {@link XWikiRepositoryModel#PROP_EXTENSION_ID} of class + * {@link XWikiRepositoryModel#EXTENSION_CLASSNAME} on whatever wiki. + */ + private static final EntityReference EXTENSIONID_PROPERTY_REFERENCE = + new EntityReference(XWikiRepositoryModel.PROP_EXTENSION_ID, EntityType.OBJECT_PROPERTY, + XWikiRepositoryModel.EXTENSION_OBJECTREFERENCE); + + private static final List EVENTS = + Arrays.asList(new XObjectPropertyAddedEvent(EXTENSIONID_PROPERTY_REFERENCE), + new XObjectPropertyDeletedEvent(EXTENSIONID_PROPERTY_REFERENCE), + new XObjectPropertyUpdatedEvent(EXTENSIONID_PROPERTY_REFERENCE)); + @Inject private Provider xcontextProvider; + @Inject + private CacheManager cacheManager; + + @Inject + private QueryManager queryManager; + + @Inject + private ObservationManager observation; + + @Inject + @Named("current") + private DocumentReferenceResolver currentStringResolver; + + /** + * Link extension id to document reference. The tabe contains null if the id link to no extension. + */ + private Cache documentReferenceCache; + @Inject private Logger logger; + private EventListener listener = new EventListener() + { + @Override + public void onEvent(Event event, Object source, Object data) + { + // TODO: Improve a bit by removing only what's changed + documentReferenceCache.removeAll(); + } + + @Override + public String getName() + { + return "repository.DefaultRepositoryManager"; + } + + @Override + public List getEvents() + { + return EVENTS; + } + }; + + @Override + public void initialize() throws InitializationException + { + // Init cache + CacheConfiguration cacheConfiguration = new CacheConfiguration(); + cacheConfiguration.setConfigurationId("repository.extensionid.documentreference"); + LRUEvictionConfiguration lru = new LRUEvictionConfiguration(); + lru.setMaxEntries(10000); + cacheConfiguration.put(LRUEvictionConfiguration.CONFIGURATIONID, lru); + + try { + this.documentReferenceCache = this.cacheManager.createNewCache(cacheConfiguration); + } catch (CacheException e) { + throw new InitializationException("Failed to initialize cache", e); + } + + // Listen to modifications + this.observation.addListener(listener, EventListener.CACHE_INVALIDATION_DEFAULT_PRIORITY); + } + + @Override + public void dispose() throws ComponentLifecycleException + { + this.observation.removeListener(listener.getName()); + this.documentReferenceCache.dispose(); + } + /** * @param the expected type of the value to return * @param xobject the xobject @@ -272,4 +380,131 @@ public BaseObject getExtensionSupporterObject(XWikiDocument extensionSupporterDo return extensionSupporterDocument.getXObject(XWikiRepositoryModel.EXTENSIONSUPPORTER_CLASSREFERENCE); } + public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument, String extensionVersion, + XWikiContext xcontext) throws XWikiException + { + if (isVersionPageEnabled(extensionDocument)) { + return xcontext.getWiki() + .getDocument(new PageReference(extensionVersion, extensionDocument.getPageReference()), xcontext); + } + + return extensionDocument; + } + + public BaseObject getExtensionVersionObject(XWikiDocument extensionVersionDocument, Extension extensionVersion, + boolean create, XWikiContext xcontext) throws XWikiException + { + // Update version object + BaseObject versionObject = + getExtensionVersionObject(extensionVersionDocument, extensionVersion.getId().getVersion()); + if (versionObject == null && create) { + versionObject = + extensionVersionDocument.newXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, xcontext); + + versionObject.set(XWikiRepositoryModel.PROP_VERSION_VERSION, + extensionVersion.getId().getVersion().getValue(), xcontext); + } + + return versionObject; + } + + public BaseObject getExtensionVersionObject(XWikiDocument document, Version version) + { + List objects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + if (objects != null) { + for (BaseObject versionObject : objects) { + if (versionObject != null) { + String versionString = + getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION, (String) null); + + if (StringUtils.isNotEmpty(versionString) && version.equals(new DefaultVersion(versionString))) { + return versionObject; + } + } + } + } + + return null; + } + + /** + * @since 42 + */ + public boolean isVersionProxyingEnabled(XWikiDocument extensionDocument) + { + BaseObject extensionProxyObject = + extensionDocument.getXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE); + if (extensionProxyObject == null) { + return false; + } + + return XWikiRepositoryModel.PROP_PROXY_PROXYLEVEL_VALUE_VERSION + .equals(getValue(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_PROXYLEVEL, (String) null)); + } + + public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws QueryException, XWikiException + { + XWikiContext xcontext = this.xcontextProvider.get(); + + DocumentReference[] cachedDocumentReference = this.documentReferenceCache.get(extensionId); + + if (cachedDocumentReference == null) { + Query query = this.queryManager.createQuery( + "select doc.fullName from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME + + ") as extension where extension." + XWikiRepositoryModel.PROP_EXTENSION_ID + " = :extensionId", + Query.XWQL); + + query.bindValue("extensionId", extensionId); + + List documentNames = query.execute(); + + if (!documentNames.isEmpty()) { + cachedDocumentReference = + new DocumentReference[] {this.currentStringResolver.resolve(documentNames.get(0))}; + } else { + cachedDocumentReference = new DocumentReference[1]; + } + + this.documentReferenceCache.set(extensionId, cachedDocumentReference); + } + + return cachedDocumentReference[0] != null ? xcontext.getWiki().getDocument(cachedDocumentReference[0], xcontext) + : null; + } + + public BaseObject getExtensionObject(XWikiDocument extensionDocument) + { + return extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); + } + + public boolean isVersionPageEnabled(String extensionId) throws QueryException, XWikiException + { + XWikiDocument document = getExistingExtensionDocumentById(extensionId); + + return isVersionPageEnabled(document); + } + + public boolean isVersionPageEnabled(XWikiDocument extensionDocument) + { + BaseObject extensionObject = getExtensionObject(extensionDocument); + + return isVersionPageEnabled(extensionObject); + } + + /** + * @param extensionOject + * @return + * @since 42 + */ + public boolean isVersionPageEnabled(BaseObject extensionOject) + { + return getBooleanValue(extensionOject, XWikiRepositoryModel.PROP_EXTENSION_VERSIONPAGE, false); + } + + public XWikiDocument getDocument(String fullName) throws XWikiException + { + XWikiContext xcontext = this.xcontextProvider.get(); + + return xcontext.getWiki().getDocument(this.currentStringResolver.resolve(fullName), xcontext); + } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index 5c110720f1a6..426cac9b05a9 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -45,16 +45,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.slf4j.Logger; -import org.xwiki.cache.Cache; -import org.xwiki.cache.CacheException; -import org.xwiki.cache.CacheManager; -import org.xwiki.cache.config.CacheConfiguration; -import org.xwiki.cache.eviction.LRUEvictionConfiguration; import org.xwiki.component.annotation.Component; -import org.xwiki.component.manager.ComponentLifecycleException; -import org.xwiki.component.phase.Disposable; -import org.xwiki.component.phase.Initializable; -import org.xwiki.component.phase.InitializationException; import org.xwiki.extension.DefaultExtensionDependency; import org.xwiki.extension.Extension; import org.xwiki.extension.ExtensionAuthor; @@ -77,7 +68,6 @@ import org.xwiki.extension.version.Version.Type; import org.xwiki.extension.version.internal.DefaultVersion; import org.xwiki.extension.version.internal.DefaultVersionConstraint; -import org.xwiki.model.EntityType; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.AttachmentReferenceResolver; import org.xwiki.model.reference.DocumentReference; @@ -85,9 +75,6 @@ import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.WikiReference; -import org.xwiki.observation.EventListener; -import org.xwiki.observation.ObservationManager; -import org.xwiki.observation.event.Event; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; @@ -102,29 +89,13 @@ import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiDocument; -import com.xpn.xwiki.internal.event.XObjectPropertyAddedEvent; -import com.xpn.xwiki.internal.event.XObjectPropertyDeletedEvent; -import com.xpn.xwiki.internal.event.XObjectPropertyUpdatedEvent; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.StringProperty; @Component(roles = RepositoryManager.class) @Singleton -public class RepositoryManager implements Initializable, Disposable +public class RepositoryManager { - /** - * The reference to match property {@link XWikiRepositoryModel#PROP_EXTENSION_ID} of class - * {@link XWikiRepositoryModel#EXTENSION_CLASSNAME} on whatever wiki. - */ - private static final EntityReference EXTENSIONID_PROPERTY_REFERENCE = - new EntityReference(XWikiRepositoryModel.PROP_EXTENSION_ID, EntityType.OBJECT_PROPERTY, - XWikiRepositoryModel.EXTENSION_OBJECTREFERENCE); - - private static final List EVENTS = - Arrays.asList(new XObjectPropertyAddedEvent(EXTENSIONID_PROPERTY_REFERENCE), - new XObjectPropertyDeletedEvent(EXTENSIONID_PROPERTY_REFERENCE), - new XObjectPropertyUpdatedEvent(EXTENSIONID_PROPERTY_REFERENCE)); - private static final Pattern PATTERN_NEWLINE = Pattern.compile("[\n\r]"); /** @@ -173,12 +144,6 @@ public class RepositoryManager implements Initializable, Disposable @Inject private RepositoryConfiguration configuration; - @Inject - private CacheManager cacheManager; - - @Inject - private ObservationManager observation; - @Inject private ExtensionFactory extensionFactory; @@ -192,116 +157,6 @@ public class RepositoryManager implements Initializable, Disposable private int maxStringPropertySize = -1; - /** - * Link extension id to document reference. The tabe contains null if the id link to no extension. - */ - private Cache documentReferenceCache; - - private EventListener listener = new EventListener() - { - @Override - public void onEvent(Event event, Object source, Object data) - { - // TODO: Improve a bit by removing only what's changed - documentReferenceCache.removeAll(); - } - - @Override - public String getName() - { - return "repository.DefaultRepositoryManager"; - } - - @Override - public List getEvents() - { - return EVENTS; - } - }; - - @Override - public void initialize() throws InitializationException - { - // Init cache - CacheConfiguration cacheConfiguration = new CacheConfiguration(); - cacheConfiguration.setConfigurationId("repository.extensionid.documentreference"); - LRUEvictionConfiguration lru = new LRUEvictionConfiguration(); - lru.setMaxEntries(10000); - cacheConfiguration.put(LRUEvictionConfiguration.CONFIGURATIONID, lru); - - try { - this.documentReferenceCache = this.cacheManager.createNewCache(cacheConfiguration); - } catch (CacheException e) { - throw new InitializationException("Failed to initialize cache", e); - } - - // Listen to modifications - this.observation.addListener(listener, EventListener.CACHE_INVALIDATION_DEFAULT_PRIORITY); - } - - @Override - public void dispose() throws ComponentLifecycleException - { - this.observation.removeListener(listener.getName()); - this.documentReferenceCache.dispose(); - } - - public XWikiDocument getDocument(String fullName) throws XWikiException - { - XWikiContext xcontext = this.xcontextProvider.get(); - - return xcontext.getWiki().getDocument(this.currentStringResolver.resolve(fullName), xcontext); - } - - public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws QueryException, XWikiException - { - XWikiContext xcontext = this.xcontextProvider.get(); - - DocumentReference[] cachedDocumentReference = this.documentReferenceCache.get(extensionId); - - if (cachedDocumentReference == null) { - Query query = this.queryManager.createQuery( - "select doc.fullName from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME - + ") as extension where extension." + XWikiRepositoryModel.PROP_EXTENSION_ID + " = :extensionId", - Query.XWQL); - - query.bindValue("extensionId", extensionId); - - List documentNames = query.execute(); - - if (!documentNames.isEmpty()) { - cachedDocumentReference = - new DocumentReference[] {this.currentStringResolver.resolve(documentNames.get(0))}; - } else { - cachedDocumentReference = new DocumentReference[1]; - } - - this.documentReferenceCache.set(extensionId, cachedDocumentReference); - } - - return cachedDocumentReference[0] != null ? xcontext.getWiki().getDocument(cachedDocumentReference[0], xcontext) - : null; - } - - public BaseObject getExtensionVersion(XWikiDocument document, Version version) - { - List objects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); - if (objects != null) { - for (BaseObject versionObject : objects) { - if (versionObject != null) { - String versionString = this.extensionStore.getValue(versionObject, - XWikiRepositoryModel.PROP_VERSION_VERSION, (String) null); - - if (StringUtils.isNotEmpty(versionString) && version.equals(new DefaultVersion(versionString))) { - return versionObject; - } - } - } - } - - return null; - } - public void validateExtension(XWikiDocument document, boolean save) throws XWikiException { BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); @@ -418,11 +273,11 @@ private DocumentReference getClassReference(XWikiDocument document, EntityRefere /** * @param document the extension document * @param extensionObject the extension object - * @param context the XWiki context + * @param xcontext the XWiki context * @return true if the extension is valid from Extension Manager point of view * @throws XWikiException unknown issue when manipulating the model */ - private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWikiContext context) + private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWikiContext xcontext) throws XWikiException { String extensionId = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID); @@ -441,7 +296,7 @@ private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWik if (extensionVersions != null) { for (BaseObject extensionVersionObject : extensionVersions) { if (extensionVersionObject != null) { - valid = isVersionValid(document, type, extensionVersionObject, context); + valid = isVersionValid(document, type, extensionVersionObject, xcontext); if (!valid) { return false; @@ -456,7 +311,7 @@ private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWik } private boolean isVersionValid(XWikiDocument document, String type, BaseObject extensionVersionObject, - XWikiContext context) + XWikiContext xcontext) { // Has a version String extensionVersion = @@ -476,7 +331,7 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e } else { ResourceReference resourceReference = null; try { - resourceReference = getDownloadReference(document, extensionVersion); + resourceReference = getDownloadReference(document, extensionVersion, xcontext); } catch (ResolveException e) { logger.debug("Cannot obtain download source reference for object [{}({})]", XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber()); @@ -494,7 +349,7 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e attachmentDocument = document; } else { attachmentDocument = - context.getWiki().getDocument(attachmentReference.getDocumentReference(), context); + xcontext.getWiki().getDocument(attachmentReference.getDocumentReference(), xcontext); } valid = attachmentDocument.getAttachment(attachmentReference.getName()) != null; @@ -532,26 +387,23 @@ public void validateExtensions() throws QueryException, XWikiException + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension", Query.XWQL); for (String documentName : query.execute()) { - validateExtension(getDocument(documentName), true); + validateExtension(this.extensionStore.getDocument(documentName), true); } } /** * @since 9.5RC1 */ - public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion) - throws ResolveException + public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion, + XWikiContext xcontext) throws ResolveException { - String downloadURL = null; - BaseObject extensionVersionObject = getExtensionVersionObject(document, extensionVersion, false); // this - // 'false' is - // important + BaseObject extensionVersionObject = getExtensionVersionObject(document, extensionVersion, false, xcontext); if (extensionVersionObject != null) { downloadURL = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD); - } else if (isVersionProxyingEnabled(document)) { + } else if (this.extensionStore.isVersionProxyingEnabled(document)) { downloadURL = resolveExtensionDownloadURL(document, extensionVersion); } @@ -642,30 +494,30 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository boolean needSave = false; - XWikiDocument document = getExistingExtensionDocumentById(extensionId); + XWikiDocument extensionDocument = this.extensionStore.getExistingExtensionDocumentById(extensionId); - if (document == null) { + if (extensionDocument == null) { // Create document - document = xcontext.getWiki().getDocument( + extensionDocument = xcontext.getWiki().getDocument( new DocumentReference(xcontext.getWikiId(), Arrays.asList("Extension", extension.getName()), "WebHome"), xcontext); - for (int i = 1; !document.isNew(); ++i) { - document = xcontext.getWiki().getDocument(new DocumentReference(xcontext.getWikiId(), + for (int i = 1; !extensionDocument.isNew(); ++i) { + extensionDocument = xcontext.getWiki().getDocument(new DocumentReference(xcontext.getWikiId(), Arrays.asList("Extension", extension.getName() + ' ' + i), "WebHome"), xcontext); } - document.readFromTemplate(this.currentResolver.resolve(XWikiRepositoryModel.EXTENSION_TEMPLATEREFERENCE), - xcontext); + extensionDocument.readFromTemplate( + this.currentResolver.resolve(XWikiRepositoryModel.EXTENSION_TEMPLATEREFERENCE), xcontext); needSave = true; } // Update document - BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); + BaseObject extensionObject = extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); if (extensionObject == null) { - extensionObject = document.newXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE, xcontext); + extensionObject = extensionDocument.newXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE, xcontext); needSave = true; } @@ -677,6 +529,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Update extension informations + needSave |= updateExtensionMain(extension, extensionObject, xcontext); needSave |= updateExtension(extension, extensionObject, xcontext); // Get former ids versions @@ -708,9 +561,11 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Proxy marker - BaseObject extensionProxyObject = document.getXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE); + BaseObject extensionProxyObject = + extensionDocument.getXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE); if (extensionProxyObject == null) { - extensionProxyObject = document.newXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE, xcontext); + extensionProxyObject = + extensionDocument.newXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE, xcontext); extensionProxyObject.setIntValue(XWikiRepositoryModel.PROP_PROXY_AUTOUPDATE, 1); needSave = true; } @@ -726,25 +581,26 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Set validVersions = new HashSet<>(); - List versionObjects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + List versionObjects = + extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); if (versionObjects != null) { for (BaseObject versionObject : versionObjects) { if (versionObject != null) { String version = this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION); - if (StringUtils.isBlank(version) || (isVersionProxyingEnabled(document) + if (StringUtils.isBlank(version) || (this.extensionStore.isVersionProxyingEnabled(extensionDocument) && !new DefaultVersion(version).equals(extension.getId().getVersion()))) { // Empty version OR old versions should be proxied - document.removeXObject(versionObject); + extensionDocument.removeXObject(versionObject); needSave = true; } else { if (!extensionVersions.containsKey(new DefaultVersion(version)) && featureVersions.containsKey(new DefaultVersion(version))) { // The version does not exist on remote repository - if (!isVersionValid(document, extension.getType(), versionObject, xcontext)) { + if (!isVersionValid(extensionDocument, extension.getType(), versionObject, xcontext)) { // The version is invalid, removing it to not make the whole extension invalid - document.removeXObject(versionObject); + extensionDocument.removeXObject(versionObject); needSave = true; } else { // The version is valid, lets keep it @@ -759,7 +615,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository } } List dependencyObjects = - document.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); + extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); if (dependencyObjects != null) { for (BaseObject dependencyObject : dependencyObjects) { if (dependencyObject != null) { @@ -768,7 +624,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository if (!validVersions.contains(version)) { // The version is invalid, removing it to not make the whole extension invalid - document.removeXObject(dependencyObject); + extensionDocument.removeXObject(dependencyObject); needSave = true; } } @@ -783,7 +639,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Give priority to extension version in case of conflict if (!extensionVersions.containsKey(version)) { - updateVersion(id, version, extension, repository, document); + updateVersion(id, version, extension, repository, extensionDocument); } } @@ -793,24 +649,24 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Version version = entry.getKey(); String id = entry.getValue(); - updateVersion(id, version, extension, repository, document); + updateVersion(id, version, extension, repository, extensionDocument); } // Save if (needSave) { - document.setAuthorReference(xcontext.getUserReference()); - if (document.isNew()) { - document.setContentAuthorReference(xcontext.getUserReference()); - document.setCreatorReference(xcontext.getUserReference()); + extensionDocument.setAuthorReference(xcontext.getUserReference()); + if (extensionDocument.isNew()) { + extensionDocument.setContentAuthorReference(xcontext.getUserReference()); + extensionDocument.setCreatorReference(xcontext.getUserReference()); } - xcontext.getWiki().saveDocument(document, + xcontext.getWiki().saveDocument(extensionDocument, "Imported extension [" + extensionId + "] from repository [" + repository.getDescriptor() + "]", true, xcontext); } - return document.getDocumentReference(); + return extensionDocument.getDocumentReference(); } private boolean updateVersion(String id, Version version, Extension extension, ExtensionRepository repository, @@ -820,7 +676,7 @@ private boolean updateVersion(String id, Version version, Extension extension, E Extension versionExtension; if (version.equals(extension.getId().getVersion())) { versionExtension = extension; - } else if (isVersionProxyingEnabled(document)) { + } else if (this.extensionStore.isVersionProxyingEnabled(document)) { return false; } else { versionExtension = repository.resolve(new ExtensionId(id, version)); @@ -837,17 +693,96 @@ private boolean updateVersion(String id, Version version, Extension extension, E } /** - * @since 9.5RC1 + * @since 42 */ - public boolean isVersionProxyingEnabled(XWikiDocument extensionDocument) + public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, XWikiContext xcontext) + throws XWikiException { - BaseObject extensionProxyObject = - extensionDocument.getXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE); - if (extensionProxyObject == null) { - return false; + return getExtensionVersionObject( + this.extensionStore.getExtensionVersionDocument(extensionDocument, version, xcontext), version, true, + xcontext); + } + + /** + * @since 42 + */ + public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying, + XWikiContext xcontext) + { + if (version == null) { + List objects = + extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + + if (objects == null || objects.isEmpty()) { + return null; + } else { + return objects.get(objects.size() - 1); + } } - return XWikiRepositoryModel.PROP_PROXY_PROXYLEVEL_VALUE_VERSION.equals(this.extensionStore - .getValue(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_PROXYLEVEL, (String) null)); + + BaseObject extensionVersionObject = extensionDocument + .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); + + if (extensionVersionObject == null && allowProxying + && this.extensionStore.isVersionProxyingEnabled(extensionDocument)) { + // No ExtensionVersionClass object for the version, but proxy is enabled, so try to find remotely + Extension extension = null; + try { + extension = resolveExtensionVersion(extensionDocument, version); + } catch (ExtensionNotFoundException e) { + this.logger.debug("No extension could be found remotely with version [{}] for extension page [{}]", + version, extensionDocument.getDocumentReference()); + } catch (ResolveException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + + // No extension could be found for the provided version + if (extension == null) { + return null; + } + + // Create a temporary xobject for that extension version + // FIXME: find a more elegant solution + try { + XWikiDocument extensionDocumentClone = extensionDocument.clone(); + updateExtension(extension, extensionVersionObject, xcontext); + updateExtensionVersion(extensionDocumentClone, extension); + extensionVersionObject = extensionDocumentClone + .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); + } catch (XWikiException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + return extensionVersionObject; + } + + private boolean updateExtensionMain(Extension extension, BaseObject extensionObject, XWikiContext xcontext) + throws XWikiException + { + boolean needSave = false; + + // Description + if (StringUtils.isEmpty(this.extensionStore.getValue(extensionObject, + XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, (String) null))) { + extensionObject.set(XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, getDescription(extension), xcontext); + needSave = true; + } + + // Issue Management + ExtensionIssueManagement issueManagement = extension.getIssueManagement(); + if (issueManagement != null) { + if (issueManagement.getSystem() != null) { + needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_SYSTEM, + issueManagement.getSystem()); + } + if (issueManagement.getURL() != null) { + needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_URL, + issueManagement.getURL()); + } + } + + return needSave; } private boolean updateExtension(Extension extension, BaseObject extensionObject, XWikiContext xcontext) @@ -878,13 +813,6 @@ private boolean updateExtension(Extension extension, BaseObject extensionObject, * update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE, extension.getWebSite()); */ - // Description - if (StringUtils.isEmpty(this.extensionStore.getValue(extensionObject, - XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, (String) null))) { - extensionObject.set(XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, getDescription(extension), xcontext); - needSave = true; - } - // License if (!extension.getLicenses().isEmpty() && !Strings.CS.equals(extension.getLicenses().iterator().next().getName(), this.extensionStore @@ -910,19 +838,6 @@ private boolean updateExtension(Extension extension, BaseObject extensionObject, } } - // Issue Management - ExtensionIssueManagement issueManagement = extension.getIssueManagement(); - if (issueManagement != null) { - if (issueManagement.getSystem() != null) { - needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_SYSTEM, - issueManagement.getSystem()); - } - if (issueManagement.getURL() != null) { - needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_URL, - issueManagement.getURL()); - } - } - // Authors needSave |= updateAuthors(extensionObject, extension.getAuthors()); @@ -1243,43 +1158,47 @@ private boolean isGivenVersionOneOfExtensionVersions(ExtensionRepository reposit .anyMatch(version -> version.getValue().equals(extensionVersion)); } - private boolean updateExtensionVersion(XWikiDocument document, Extension extension) throws XWikiException + private boolean updateExtensionVersion(XWikiDocument document, Extension extensionVersion) throws XWikiException { boolean needSave = false; XWikiContext xcontext = this.xcontextProvider.get(); // Update version object - BaseObject versionObject = getExtensionVersion(document, extension.getId().getVersion()); + BaseObject versionObject = + this.extensionStore.getExtensionVersionObject(document, extensionVersion.getId().getVersion()); if (versionObject == null) { versionObject = document.newXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, xcontext); - versionObject.set(XWikiRepositoryModel.PROP_VERSION_VERSION, extension.getId().getVersion().getValue(), - xcontext); + versionObject.set(XWikiRepositoryModel.PROP_VERSION_VERSION, + extensionVersion.getId().getVersion().getValue(), xcontext); needSave = true; } // Id - needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_ID, extension.getId().getId()); + needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_ID, extensionVersion.getId().getId()); // Features - needSave |= - updateFeatures(XWikiRepositoryModel.PROP_VERSION_FEATURES, versionObject, extension.getExtensionFeatures()); + needSave |= updateFeatures(XWikiRepositoryModel.PROP_VERSION_FEATURES, versionObject, + extensionVersion.getExtensionFeatures()); // Repositories - List repositories = XWikiRepositoryModel.toStringList(extension.getRepositories()); + List repositories = XWikiRepositoryModel.toStringList(extensionVersion.getRepositories()); needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES, repositories); // Update dependencies - needSave |= updateExtensionVersionDependencies(document, extension); + needSave |= updateExtensionVersionDependencies(document, extensionVersion); // Download - if (!StringUtils.isEmpty(extension.getType())) { - String download = getDownloadURL(extension); + if (!StringUtils.isEmpty(extensionVersion.getType())) { + String download = getDownloadURL(extensionVersion); needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD, download); } + // Common properties + updateExtension(extensionVersion, versionObject, xcontext); + return needSave; } @@ -1326,10 +1245,8 @@ protected boolean updateProperties(BaseObject object, Extension extension) throw protected boolean update(BaseObject object, String fieldName, Object value) throws XWikiException { // Make sure collection are lists - if (value instanceof Collection) { - if (!(value instanceof List)) { - value = new ArrayList<>((Collection) value); - } + if (value instanceof List list) { + value = new ArrayList<>(list); } if (ObjectUtils.notEqual(value, this.extensionStore.getValue(object, fieldName))) { @@ -1353,63 +1270,4 @@ private boolean updateCollection(BaseObject extensionObject, String fieldName, C return needSave; } - - /** - * @since 9.5RC1 - */ - public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version) - { - return getExtensionVersionObject(extensionDocument, version, true); - } - - /** - * @since 9.5RC1 - */ - public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying) - { - if (version == null) { - List objects = - extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); - - if (objects == null || objects.isEmpty()) { - return null; - } else { - return objects.get(objects.size() - 1); - } - } - - BaseObject extensionVersionObject = extensionDocument - .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); - - if (extensionVersionObject == null && allowProxying && isVersionProxyingEnabled(extensionDocument)) { - // No ExtensionVersionClass object for the version, but proxy is enabled, so try to find remotely - Extension extension = null; - try { - extension = resolveExtensionVersion(extensionDocument, version); - } catch (ExtensionNotFoundException e) { - this.logger.debug("No extension could be found remotely with version [{}] for extension page [{}]", - version, extensionDocument.getDocumentReference()); - } catch (ResolveException e) { - throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); - } - - // No extension could be found for the provided version - if (extension == null) { - return null; - } - - // Create a temporary xobject for that extension version - // FIXME: find a more elegant solution - try { - XWikiDocument extensionDocumentClone = extensionDocument.clone(); - updateExtensionVersion(extensionDocumentClone, extension); - extensionVersionObject = extensionDocumentClone - .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); - } catch (XWikiException e) { - throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); - } - } - - return extensionVersionObject; - } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java index 3eeed847aea7..537d3158388b 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java @@ -187,6 +187,11 @@ public class XWikiRepositoryModel public static final String PROP_EXTENSION_PROPERTIES = "properties"; + /** + * @since 42 + */ + public static final String PROP_EXTENSION_VERSIONPAGE = "versionPage"; + public static final String PROP_VERSION_ID = "id"; public static final String PROP_VERSION_VERSION = "version"; From c896e25c397ac397ecd0ec145e7dcd182a65fc78 Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Thu, 25 Sep 2025 16:23:55 +0300 Subject: [PATCH 03/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * import of new extensions (and a bit of migration) --- .../repository/internal/ExtensionStore.java | 41 ++++++++- .../internal/RepositoryManager.java | 86 ++++++++++++------- .../internal/XWikiRepositoryModel.java | 5 ++ .../AbstractExtensionRESTResource.java | 77 ++++++++--------- .../ExtensionVersionFileRESTResource.java | 5 +- .../ExtensionVersionsRESTResource.java | 10 +-- 6 files changed, 144 insertions(+), 80 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 9599f268d7c5..308029ef241c 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -380,12 +380,21 @@ public BaseObject getExtensionSupporterObject(XWikiDocument extensionSupporterDo return extensionSupporterDocument.getXObject(XWikiRepositoryModel.EXTENSIONSUPPORTER_CLASSREFERENCE); } + public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument, Version extensionVersion, + XWikiContext xcontext) throws XWikiException + { + return getExtensionVersionDocument(extensionDocument, extensionVersion.getValue(), xcontext); + } + public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument, String extensionVersion, XWikiContext xcontext) throws XWikiException { if (isVersionPageEnabled(extensionDocument)) { return xcontext.getWiki() - .getDocument(new PageReference(extensionVersion, extensionDocument.getPageReference()), xcontext); + .getDocument( + new PageReference(extensionVersion, new PageReference( + XWikiRepositoryModel.EXTENSIONVERSIONS_SPACENAME, extensionDocument.getPageReference())), + xcontext); } return extensionDocument; @@ -408,6 +417,11 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionVersionDocume return versionObject; } + public BaseObject getExtensionVersionObject(XWikiDocument document, String version) + { + return getExtensionVersionObject(document, new DefaultVersion(version)); + } + public BaseObject getExtensionVersionObject(XWikiDocument document, Version version) { List objects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); @@ -477,6 +491,9 @@ public BaseObject getExtensionObject(XWikiDocument extensionDocument) return extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); } + /** + * @since 42 + */ public boolean isVersionPageEnabled(String extensionId) throws QueryException, XWikiException { XWikiDocument document = getExistingExtensionDocumentById(extensionId); @@ -484,6 +501,9 @@ public boolean isVersionPageEnabled(String extensionId) throws QueryException, X return isVersionPageEnabled(document); } + /** + * @since 42 + */ public boolean isVersionPageEnabled(XWikiDocument extensionDocument) { BaseObject extensionObject = getExtensionObject(extensionDocument); @@ -492,8 +512,6 @@ public boolean isVersionPageEnabled(XWikiDocument extensionDocument) } /** - * @param extensionOject - * @return * @since 42 */ public boolean isVersionPageEnabled(BaseObject extensionOject) @@ -501,6 +519,23 @@ public boolean isVersionPageEnabled(BaseObject extensionOject) return getBooleanValue(extensionOject, XWikiRepositoryModel.PROP_EXTENSION_VERSIONPAGE, false); } + /** + * @since 42 + */ + public boolean setVersionPageEnabled(BaseObject extensionOject) + { + if (!isVersionPageEnabled(extensionOject)) { + extensionOject.setIntValue(XWikiRepositoryModel.PROP_EXTENSION_VERSIONPAGE, 1); + + return true; + } + + return false; + } + + /** + * @since 42 + */ public XWikiDocument getDocument(String fullName) throws XWikiException { XWikiContext xcontext = this.xcontextProvider.get(); diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index 426cac9b05a9..b1f411164763 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -331,7 +331,7 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e } else { ResourceReference resourceReference = null; try { - resourceReference = getDownloadReference(document, extensionVersion, xcontext); + resourceReference = getDownloadReference(document, extensionVersion); } catch (ResolveException e) { logger.debug("Cannot obtain download source reference for object [{}({})]", XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber()); @@ -392,14 +392,14 @@ public void validateExtensions() throws QueryException, XWikiException } /** - * @since 9.5RC1 + * @since 42 */ - public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion, - XWikiContext xcontext) throws ResolveException + public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion) + throws ResolveException { String downloadURL = null; - BaseObject extensionVersionObject = getExtensionVersionObject(document, extensionVersion, false, xcontext); + BaseObject extensionVersionObject = getExtensionVersionObject(document, extensionVersion, false); if (extensionVersionObject != null) { downloadURL = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD); @@ -511,11 +511,14 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository this.currentResolver.resolve(XWikiRepositoryModel.EXTENSION_TEMPLATEREFERENCE), xcontext); needSave = true; + } else { + // Avoid modifying the cached document + extensionDocument = extensionDocument.clone(); } // Update document - BaseObject extensionObject = extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); + BaseObject extensionObject = this.extensionStore.getExtensionObject(extensionDocument); if (extensionObject == null) { extensionObject = extensionDocument.newXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE, xcontext); needSave = true; @@ -527,10 +530,14 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository needSave = true; } - // Update extension informations + // Make sure we use the dedicated version page mode + needSave |= this.extensionStore.setVersionPageEnabled(extensionObject); + + // Update main extension page - needSave |= updateExtensionMain(extension, extensionObject, xcontext); needSave |= updateExtension(extension, extensionObject, xcontext); + needSave |= updateExtensionMain(extension, extensionObject, xcontext); + needSave |= updateExtensionVersionDependencies(extension, extensionDocument); // Get former ids versions TreeMap featureVersions = new TreeMap<>(); @@ -577,10 +584,9 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository needSave |= update(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_REPOSITORYURI, repository.getDescriptor().getURI().toString()); - // Remove unexisting versions - Set validVersions = new HashSet<>(); + // Remove unexisting versions List versionObjects = extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); if (versionObjects != null) { @@ -591,7 +597,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository if (StringUtils.isBlank(version) || (this.extensionStore.isVersionProxyingEnabled(extensionDocument) && !new DefaultVersion(version).equals(extension.getId().getVersion()))) { - // Empty version OR old versions should be proxied + // Empty version OR lost versions should be proxied extensionDocument.removeXObject(versionObject); needSave = true; } else { @@ -614,6 +620,11 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository } } } + + // Current version is valid + validVersions.add(extension.getId().getVersion().getValue()); + + // Cleanup dependencies associated to invalid versions List dependencyObjects = extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); if (dependencyObjects != null) { @@ -639,7 +650,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Give priority to extension version in case of conflict if (!extensionVersions.containsKey(version)) { - updateVersion(id, version, extension, repository, extensionDocument); + //updateVersion(id, version, extension, repository, extensionDocument); } } @@ -649,7 +660,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Version version = entry.getKey(); String id = entry.getValue(); - updateVersion(id, version, extension, repository, extensionDocument); + // updateVersion(id, version, extension, repository, extensionDocument); } // Save @@ -670,20 +681,20 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository } private boolean updateVersion(String id, Version version, Extension extension, ExtensionRepository repository, - XWikiDocument document) + XWikiDocument extensionDocument) { try { Extension versionExtension; if (version.equals(extension.getId().getVersion())) { versionExtension = extension; - } else if (this.extensionStore.isVersionProxyingEnabled(document)) { + } else if (this.extensionStore.isVersionProxyingEnabled(extensionDocument)) { return false; } else { versionExtension = repository.resolve(new ExtensionId(id, version)); } // Update version related informations - return updateExtensionVersion(document, versionExtension); + return updateExtensionVersion(versionExtension, extensionDocument, true); } catch (Exception e) { this.logger.error("Failed to resolve extension with id [" + id + "] and version [" + version + "] on repository [" + repository + "]", e); @@ -699,15 +710,13 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str throws XWikiException { return getExtensionVersionObject( - this.extensionStore.getExtensionVersionDocument(extensionDocument, version, xcontext), version, true, - xcontext); + this.extensionStore.getExtensionVersionDocument(extensionDocument, version, xcontext), version, true); } /** * @since 42 */ - public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying, - XWikiContext xcontext) + public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying) { if (version == null) { List objects = @@ -741,12 +750,11 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str return null; } - // Create a temporary xobject for that extension version + // Create a "detached" xobject for that extension version // FIXME: find a more elegant solution try { XWikiDocument extensionDocumentClone = extensionDocument.clone(); - updateExtension(extension, extensionVersionObject, xcontext); - updateExtensionVersion(extensionDocumentClone, extension); + updateExtensionVersion(extension, extensionDocumentClone, false); extensionVersionObject = extensionDocumentClone .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); } catch (XWikiException e) { @@ -1006,7 +1014,7 @@ private String resolveAuthorIdOnWiki(String wiki, String authorName, String[] au return null; } - private boolean updateExtensionVersionDependencies(XWikiDocument document, Extension extension) + private boolean updateExtensionVersionDependencies(Extension extension, XWikiDocument document) throws XWikiException { boolean needSave = false; @@ -1158,17 +1166,32 @@ private boolean isGivenVersionOneOfExtensionVersions(ExtensionRepository reposit .anyMatch(version -> version.getValue().equals(extensionVersion)); } - private boolean updateExtensionVersion(XWikiDocument document, Extension extensionVersion) throws XWikiException + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, boolean save) + throws XWikiException { boolean needSave = false; XWikiContext xcontext = this.xcontextProvider.get(); + // Resolve the version document + XWikiDocument extensionVersionDocument = this.extensionStore.getExtensionVersionDocument(extensiondocument, + extensionVersion.getId().getVersion(), xcontext); + if (extensionVersionDocument.isNew()) { + // New document version + needSave = true; + } + + // Avoid modifying the cached document + if (save) { + extensionVersionDocument = extensionVersionDocument.clone(); + } + // Update version object - BaseObject versionObject = - this.extensionStore.getExtensionVersionObject(document, extensionVersion.getId().getVersion()); + BaseObject versionObject = this.extensionStore.getExtensionVersionObject(extensionVersionDocument, + extensionVersion.getId().getVersion()); if (versionObject == null) { - versionObject = document.newXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, xcontext); + versionObject = + extensionVersionDocument.newXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, xcontext); versionObject.set(XWikiRepositoryModel.PROP_VERSION_VERSION, extensionVersion.getId().getVersion().getValue(), xcontext); @@ -1188,7 +1211,7 @@ private boolean updateExtensionVersion(XWikiDocument document, Extension extensi needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES, repositories); // Update dependencies - needSave |= updateExtensionVersionDependencies(document, extensionVersion); + needSave |= updateExtensionVersionDependencies(extensionVersion, extensionVersionDocument); // Download if (!StringUtils.isEmpty(extensionVersion.getType())) { @@ -1199,6 +1222,11 @@ private boolean updateExtensionVersion(XWikiDocument document, Extension extensi // Common properties updateExtension(extensionVersion, versionObject, xcontext); + // Save if dedicated version page + if (save && needSave && this.extensionStore.isVersionPageEnabled(extensiondocument)) { + xcontext.getWiki().saveDocument(extensionVersionDocument, "Update", xcontext); + } + return needSave; } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java index 537d3158388b..346f9e83b797 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java @@ -51,6 +51,11 @@ public class XWikiRepositoryModel */ public static final String EXTENSION_SPACENAME = "ExtensionCode"; + /** + * @since 42 + */ + public static final String EXTENSIONVERSIONS_SPACENAME = "Versions"; + public static final String EXTENSION_CLASSNAME = EXTENSION_SPACENAME + ".ExtensionClass"; public static final String AVERAGERATING_CLASSNAME = "XWiki.AverageRatingsClass"; diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java index 828278fc1c56..f1da4b724a9a 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java @@ -97,30 +97,22 @@ public abstract class AbstractExtensionRESTResource extends XWikiResource implem protected static final String DEFAULT_FL; - protected static final Map EPROPERTIES_INDEX = Map.of( - XWikiRepositoryModel.PROP_EXTENSION_ID, 0, - XWikiRepositoryModel.PROP_EXTENSION_NAME, 1, - XWikiRepositoryModel.PROP_EXTENSION_TYPE, 2, - XWikiRepositoryModel.PROP_VERSION_VERSION, 3 - ); + protected static final Map EPROPERTIES_INDEX = + Map.of(XWikiRepositoryModel.PROP_EXTENSION_ID, 0, XWikiRepositoryModel.PROP_EXTENSION_NAME, 1, + XWikiRepositoryModel.PROP_EXTENSION_TYPE, 2, XWikiRepositoryModel.PROP_VERSION_VERSION, 3); // Note that the order of the retrieved information is stable and documented by EPROPERTIES_INDEX, // therefore it shouldn't be changed or it would break some of the APIs relying on it. - protected static final String SELECT_EXTENSIONSUMMARY = String.format("doc.name, doc.space, " - + "extension.%s, extension.%s, extension.%s", - XWikiRepositoryModel.PROP_EXTENSION_ID, - XWikiRepositoryModel.PROP_EXTENSION_NAME, - XWikiRepositoryModel.PROP_EXTENSION_TYPE); - - protected static final String SELECT_EXTENSION_VERSION = String.format("doc.name, doc.space, " - + "extensionVersion.%s, extensionVersion.%s, extensionVersion.%s, " - + "extensionVersion.%s, extensionVersion.%s", - XWikiRepositoryModel.PROP_EXTENSION_ID, - XWikiRepositoryModel.PROP_EXTENSION_NAME, - XWikiRepositoryModel.PROP_EXTENSION_TYPE, - XWikiRepositoryModel.PROP_VERSION_VERSION, - XWikiRepositoryModel.PROP_VERSION_INDEX); + protected static final String SELECT_EXTENSIONSUMMARY = String.format( + "doc.name, doc.space, " + "extension.%s, extension.%s, extension.%s", XWikiRepositoryModel.PROP_EXTENSION_ID, + XWikiRepositoryModel.PROP_EXTENSION_NAME, XWikiRepositoryModel.PROP_EXTENSION_TYPE); + protected static final String SELECT_EXTENSION_VERSION = String.format( + "doc.name, doc.space, " + "extensionVersion.%s, extensionVersion.%s, extensionVersion.%s, " + + "extensionVersion.%s, extensionVersion.%s", + XWikiRepositoryModel.PROP_EXTENSION_ID, XWikiRepositoryModel.PROP_EXTENSION_NAME, + XWikiRepositoryModel.PROP_EXTENSION_TYPE, XWikiRepositoryModel.PROP_VERSION_VERSION, + XWikiRepositoryModel.PROP_VERSION_INDEX); static { // Solr @@ -186,7 +178,7 @@ public void initialize() throws InitializationException public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws QueryException, XWikiException { - XWikiDocument document = this.repositoryManager.getExistingExtensionDocumentById(extensionId); + XWikiDocument document = this.extensionStore.getExistingExtensionDocumentById(extensionId); if (document == null) { throw new WebApplicationException(Status.NOT_FOUND); @@ -209,8 +201,8 @@ protected long getExtensionsCountResult(Query query) throws QueryException return ((Number) query.execute().get(0)).intValue(); } - protected Query createExtensionsSummariesQuery(String from, String where, int offset, int number, - boolean versions, boolean pageVersion) throws QueryException + protected Query createExtensionsSummariesQuery(String from, String where, int offset, int number, boolean versions, + boolean pageVersion) throws QueryException { String select = SELECT_EXTENSIONSUMMARY; @@ -237,8 +229,7 @@ private Query createExtensionsQuery(String select, String from, String where, in // from if (!pageVersion) { - queryStr - .append(", doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension"); + queryStr.append(", doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension"); } if (versions) { queryStr @@ -322,9 +313,11 @@ protected void addLicense(AbstractExtension extension, String licenseName) } protected E createExtension(XWikiDocument extensionDocument, String version) + throws XWikiException { BaseObject extensionObject = getExtensionObject(extensionDocument); - XWikiDocument versionDocument = this.repositoryManager.getExtensionVersionDocument(extensionDocument, version); + XWikiDocument versionDocument = + this.extensionStore.getExtensionVersionDocument(extensionDocument, version, getXWikiContext()); DocumentReference extensionDocumentReference = extensionDocument.getDocumentReference(); if (extensionObject == null) { @@ -339,7 +332,8 @@ protected E createExtension(XWikiDocument extensio extensionVersion = null; extensionVersionObject = extensionObject; } else { - extensionVersionObject = repositoryManager.getExtensionVersionObject(extensionDocument, version); + extensionVersionObject = + this.repositoryManager.getExtensionVersionObject(extensionDocument, version, getXWikiContext()); if (extensionVersionObject == null) { throw new WebApplicationException(Status.NOT_FOUND); @@ -347,16 +341,19 @@ protected E createExtension(XWikiDocument extensio extensionVersion = this.extensionObjectFactory.createExtensionVersion(); extension = extensionVersion; - extensionVersion.setVersion(this.extensionStore.getValue(extensionVersionObject, - XWikiRepositoryModel.PROP_VERSION_VERSION)); + extensionVersion.setVersion( + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION)); } extension.setId(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_ID)); - extension.setType(StringUtils.stripToNull(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); + extension.setType(StringUtils.stripToNull( + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); extension.setRating(getExtensionRating(extensionDocumentReference)); - extension.setSummary(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); - extension.setDescription(this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION)); + extension.setSummary( + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); + extension.setDescription( + this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION)); extension .setName(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_NAME)); extension.setCategory( @@ -397,8 +394,8 @@ protected E createExtension(XWikiDocument extensio this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS)); // Features - List features = this.extensionStore.getValue(extensionVersionObject, - XWikiRepositoryModel.PROP_VERSION_FEATURES); + List features = + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_FEATURES); extractFeatureInformation(features, extension); @@ -407,8 +404,8 @@ protected E createExtension(XWikiDocument extensio this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_LICENSENAME)); // Allowed namespaces - List namespaces = this.extensionStore.getValue(extensionObject, - XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES); + List namespaces = + this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES); Integer namespacesEmpty = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES_EMPTY, 0); if (namespaces != null && (!namespaces.isEmpty() || namespacesEmpty == 1)) { @@ -418,12 +415,12 @@ protected E createExtension(XWikiDocument extensio } // Properties - addProperties(extension, this.extensionStore.getValue(extensionObject, - XWikiRepositoryModel.PROP_EXTENSION_PROPERTIES)); + addProperties(extension, + this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_PROPERTIES)); if (extensionVersion != null) { - List repositories = this.extensionStore.getValue(extensionVersionObject, - XWikiRepositoryModel.PROP_VERSION_REPOSITORIES); + List repositories = + this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES); extensionVersion.withRepositories(toExtensionRepositories(repositories)); // Dependencies diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionFileRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionFileRESTResource.java index c40cd6f6d1d7..f2d21554031f 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionFileRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionFileRESTResource.java @@ -115,7 +115,7 @@ private ResponseBuilder downloadLocalExtension(String extensionId, String extens checkRights(extensionDocument); ResourceReference resourceReference = - repositoryManager.getDownloadReference(extensionDocument, extensionVersion); + this.repositoryManager.getDownloadReference(extensionDocument, extensionVersion); ResponseBuilder response = null; @@ -166,7 +166,8 @@ private ResponseBuilder downloadLocalExtension(String extensionId, String extens response.type(type); BaseObject extensionObject = getExtensionObject(extensionDocument); - String extensionType = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE); + String extensionType = + this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE); response.entity(entity.getContent()); response.header("Content-Disposition", "attachment; filename=\"" + extensionId + '-' + extensionVersion + '.' + extensionType + "\""); diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java index 17fa971f1500..c466d46b94e7 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java @@ -21,12 +21,8 @@ package org.xwiki.repository.internal.resources; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import javax.inject.Named; import javax.ws.rs.DefaultValue; @@ -40,13 +36,14 @@ import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersionSummary; import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersions; import org.xwiki.extension.version.InvalidVersionRangeException; -import org.xwiki.extension.version.Version; import org.xwiki.extension.version.VersionConstraint; import org.xwiki.extension.version.internal.DefaultVersion; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.repository.Resources; +import com.xpn.xwiki.XWikiException; + /** * @version $Id$ * @since 3.2M3 @@ -60,7 +57,8 @@ public class ExtensionVersionsRESTResource extends AbstractExtensionRESTResource public ExtensionVersions getExtensionVersions(@PathParam("extensionId") String extensionId, @QueryParam(Resources.QPARAM_LIST_START) @DefaultValue("0") int offset, @QueryParam(Resources.QPARAM_LIST_NUMBER) @DefaultValue("-1") int number, - @QueryParam(Resources.QPARAM_VERSIONS_RANGES) String ranges) throws QueryException, InvalidVersionRangeException + @QueryParam(Resources.QPARAM_VERSIONS_RANGES) String ranges) + throws QueryException, InvalidVersionRangeException, XWikiException { boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionId); Query query = From 417c1b76e7ef9851f597b991519ea5afaf0b29ed Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Thu, 25 Sep 2025 16:46:09 +0300 Subject: [PATCH 04/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * remove debug comments --- .../java/org/xwiki/repository/internal/RepositoryManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index b1f411164763..edba6fea9ad6 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -650,7 +650,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Give priority to extension version in case of conflict if (!extensionVersions.containsKey(version)) { - //updateVersion(id, version, extension, repository, extensionDocument); + updateVersion(id, version, extension, repository, extensionDocument); } } @@ -660,7 +660,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Version version = entry.getKey(); String id = entry.getValue(); - // updateVersion(id, version, extension, repository, extensionDocument); + updateVersion(id, version, extension, repository, extensionDocument); } // Save From b29e8b8187976039ca1241023fad4139e547b9ac Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 25 Sep 2025 16:39:33 +0200 Subject: [PATCH 05/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Provide a livedata to display the various versions --- .../ExtensionCode/ExtensionSheet.xml | 55 ++++++++++----- .../resources/ExtensionCode/VersionsHome.xml | 68 +++++++++++++++++++ 2 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml index b5f3effc5807..41108ef3210d 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml @@ -20,7 +20,7 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. --> - + ExtensionCode ExtensionSheet @@ -304,22 +304,29 @@ ## Only display release notes if there are downloads and release notes #if ($lastVersionObject) - #set ($releaseNotes = []) - #set ($versions = $doc.getObjects("ExtensionCode.ExtensionVersionClass")) - #foreach ($versionObject in $versions) - #set ($notes = $!{versionObject.display('notes', 'read')}) - #set ($version = $!{versionObject.getProperty('version').value}) - #if ("$!notes" != '' && "$!version" != '') - #set ($discard = $releaseNotes.add([$version, $notes])) + #if ($doc.getValue('versionPage') == 1) + #set ($versionPageRef = $services.model.createEntityReference('Versions', 'page', $doc.pageReference)) + = Versions = + + {{include page="$services.rendering.escape($services.model.serialize($versionPageRef), 'xwiki/2.1')"/}} + #else + #set ($releaseNotes = []) + #set ($versions = $doc.getObjects("ExtensionCode.ExtensionVersionClass")) + #foreach ($versionObject in $versions) + #set ($notes = $!{versionObject.display('notes', 'read')}) + #set ($version = $!{versionObject.getProperty('version').value}) + #if ("$!notes" != '' && "$!version" != '') + #set ($discard = $releaseNotes.add([$version, $notes])) + #end #end - #end - ## Reverse the list to have latest versions first - #set ($discard = $collectiontool.reverseModifiable($releaseNotes)) - #if (!$releaseNotes.isEmpty()) - = Release Notes = - #foreach ($entry in $releaseNotes) - == v$services.rendering.escape($entry.get(0), 'xwiki/2.1') == - $entry.get(1) + ## Reverse the list to have latest versions first + #set ($discard = $collectiontool.reverseModifiable($releaseNotes)) + #if (!$releaseNotes.isEmpty()) + = Release Notes = + #foreach ($entry in $releaseNotes) + == v$services.rendering.escape($entry.get(0), 'xwiki/2.1') == + $entry.get(1) + #end #end #end @@ -405,8 +412,11 @@ 0 + long 0 select + forbidden + 0 0 cache 5 @@ -426,6 +436,7 @@ code 2 Code + 0 20 50 0 @@ -435,6 +446,8 @@ 0 0 select + forbidden + 0 0 contentType 6 @@ -470,6 +483,8 @@ 0 0 select + forbidden + 0 0 use 3 @@ -545,8 +560,11 @@ 0 + long 0 select + forbidden + 0 0 cache 5 @@ -566,6 +584,7 @@ code 2 Code + 0 20 50 0 @@ -575,6 +594,8 @@ 0 0 select + forbidden + 0 0 contentType 6 @@ -610,6 +631,8 @@ 0 0 select + forbidden + 0 0 use 3 diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml new file mode 100644 index 000000000000..70cdbd7aaed4 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml @@ -0,0 +1,68 @@ + + + + + + ExtensionCode + VersionsHome + + + 0 + xwiki:XWiki.Admin + xwiki:XWiki.Admin + xwiki:XWiki.Admin + 1.1 + + <comment/> + <minorEdit>false</minorEdit> + <syntaxId>xwiki/2.1</syntaxId> + <hidden>true</hidden> + <content>{{velocity}} +#set($locationCriteria = $doc.space) +#if(!$locationCriteria.endsWith('Versions')) + #set ($locationCriteria = "${locationCriteria}.Versions") +#end + +#set ($optionsLD = { + 'className': 'ExtensionCode.ExtensionVersionClass', + 'location': "${locationCriteria}" +}) + +{{liveData + id="versions" + properties="version,notes" + source="liveTable" + sourceParameters="$services.rendering.escape($escapetool.url($optionsLD), 'xwiki/2.1')" +}}{ +"meta": { + "propertyDescriptors": [ + { + "id": "version", + "displayer": {"id": "link", "propertyHref": "doc.url"}, + "editable": false + } + ] + } +} +{{/liveData}} + +{{/velocity}}</content> +</xwikidoc> From a86c233fee19e245ca33d3e93c6824b905116bab Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 25 Sep 2025 16:57:24 +0200 Subject: [PATCH 06/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Add some doc --- .../repository/internal/ExtensionStore.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 308029ef241c..1e130e8242e3 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -84,6 +84,7 @@ */ @Component(roles = ExtensionStore.class) @Singleton +@SuppressWarnings("checkstyle:ClassFanOutComplexity") public class ExtensionStore implements Initializable, Disposable { /** @@ -380,12 +381,30 @@ public BaseObject getExtensionSupporterObject(XWikiDocument extensionSupporterDo return extensionSupporterDocument.getXObject(XWikiRepositoryModel.EXTENSIONSUPPORTER_CLASSREFERENCE); } + /** + * Retrieve the extension version document for the given extension document and the given version. + * @param extensionDocument the document of the extension + * @param extensionVersion the version for which to retrieve the document + * @param xcontext the current context + * @return the version document or the given extension document + * @throws XWikiException in case of problem for loading the document + * @since 17.9.0RC1 + */ public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument, Version extensionVersion, XWikiContext xcontext) throws XWikiException { return getExtensionVersionDocument(extensionDocument, extensionVersion.getValue(), xcontext); } + /** + * Retrieve the extension version document for the given extension document and the given version. + * @param extensionDocument the document of the extension + * @param extensionVersion the version for which to retrieve the document + * @param xcontext the current context + * @return the version document or the given extension document + * @throws XWikiException in case of problem for loading the document + * @since 17.9.0RC1 + */ public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument, String extensionVersion, XWikiContext xcontext) throws XWikiException { @@ -400,6 +419,16 @@ public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument return extensionDocument; } + /** + * Retrieve the xobject of the version for given extension. + * @param extensionVersionDocument the document that might contain the version object + * @param extensionVersion the version for which to retrieve the object + * @param create {@code true} to create the object if it doesn't exist + * @param xcontext the current context + * @return the version object or {@code null} if it doesn't exist and shouldn't be created + * @throws XWikiException in case of problem for loading the document + * @since 17.9.0RC1 + */ public BaseObject getExtensionVersionObject(XWikiDocument extensionVersionDocument, Extension extensionVersion, boolean create, XWikiContext xcontext) throws XWikiException { @@ -417,11 +446,25 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionVersionDocume return versionObject; } + /** + * Retrieve the xobject of the version from the given document. + * @param document the document where to retrieve the version object. + * @param version the version for which to retrieve the object + * @return the version object or {@code null} if it doesn't exist and shouldn't be created + * @since 17.9.0RC1 + */ public BaseObject getExtensionVersionObject(XWikiDocument document, String version) { return getExtensionVersionObject(document, new DefaultVersion(version)); } + /** + * Retrieve the xobject of the version from the given document. + * @param document the document where to retrieve the version object. + * @param version the version for which to retrieve the object + * @return the version object or {@code null} if it doesn't exist and shouldn't be created + * @since 17.9.0RC1 + */ public BaseObject getExtensionVersionObject(XWikiDocument document, Version version) { List<BaseObject> objects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); From 390ac3eaee667a50d1056132324ffa7446a3bf92 Mon Sep 17 00:00:00 2001 From: Thomas Mortagne <thomas.mortagne@gmail.com> Date: Thu, 25 Sep 2025 17:58:17 +0300 Subject: [PATCH 07/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * properly migrate existing release notes --- .../internal/RepositoryManager.java | 72 +++++++++++++++---- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index edba6fea9ad6..f625b7e187da 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -586,6 +586,9 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Set<String> validVersions = new HashSet<>(); + // Check the versions storage mode + boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionObject); + // Remove unexisting versions List<BaseObject> versionObjects = extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); @@ -595,14 +598,25 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository String version = this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION); - if (StringUtils.isBlank(version) || (this.extensionStore.isVersionProxyingEnabled(extensionDocument) - && !new DefaultVersion(version).equals(extension.getId().getVersion()))) { - // Empty version OR lost versions should be proxied + // Remove the object if: + // * versions should be stored in dedicated pages + // * the version is blank + // * versions should be proxied + if (versionPageEnabled || StringUtils.isBlank(version) + || (this.extensionStore.isVersionProxyingEnabled(extensionDocument) + && !new DefaultVersion(version).equals(extension.getId().getVersion()))) { extensionDocument.removeXObject(versionObject); needSave = true; + + // When moving from legacy storage to dedicated version page storage, we need to migrate the + // object and not just deleted it + if (versionPageEnabled) { + moveLegacyVersion(extensionDocument, versionObject); + } } else { if (!extensionVersions.containsKey(new DefaultVersion(version)) && featureVersions.containsKey(new DefaultVersion(version))) { + // Version are stored on dedicated pages // The version does not exist on remote repository if (!isVersionValid(extensionDocument, extension.getType(), versionObject, xcontext)) { // The version is invalid, removing it to not make the whole extension invalid @@ -624,7 +638,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // Current version is valid validVersions.add(extension.getId().getVersion().getValue()); - // Cleanup dependencies associated to invalid versions + // Cleanup dependencies not associated to valid versions List<BaseObject> dependencyObjects = extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE); if (dependencyObjects != null) { @@ -660,26 +674,31 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Version version = entry.getKey(); String id = entry.getValue(); - updateVersion(id, version, extension, repository, extensionDocument); + updateVersion(id, version, extension, repository, extensionDocument); } // Save if (needSave) { - extensionDocument.setAuthorReference(xcontext.getUserReference()); - if (extensionDocument.isNew()) { - extensionDocument.setContentAuthorReference(xcontext.getUserReference()); - extensionDocument.setCreatorReference(xcontext.getUserReference()); - } - - xcontext.getWiki().saveDocument(extensionDocument, - "Imported extension [" + extensionId + "] from repository [" + repository.getDescriptor() + "]", true, + saveDocument(extensionDocument, + "Imported extension [" + extensionId + "] from repository [" + repository.getDescriptor() + "]", xcontext); } return extensionDocument.getDocumentReference(); } + private void saveDocument(XWikiDocument document, String comment, XWikiContext xcontext) throws XWikiException + { + document.setAuthorReference(xcontext.getUserReference()); + if (document.isNew()) { + document.setContentAuthorReference(xcontext.getUserReference()); + document.setCreatorReference(xcontext.getUserReference()); + } + + xcontext.getWiki().saveDocument(document, comment, xcontext); + } + private boolean updateVersion(String id, Version version, Extension extension, ExtensionRepository repository, XWikiDocument extensionDocument) { @@ -1166,6 +1185,23 @@ private boolean isGivenVersionOneOfExtensionVersions(ExtensionRepository reposit .anyMatch(version -> version.getValue().equals(extensionVersion)); } + private void moveLegacyVersion(XWikiDocument extensionDocument, BaseObject versionObject) throws XWikiException + { + XWikiContext xcontext = this.xcontextProvider.get(); + + // Resolve the version + String version = versionObject.getStringValue(XWikiRepositoryModel.PROP_VERSION_VERSION); + + // Resolve the version document + XWikiDocument extensionVersionDocument = this.extensionStore + .getExtensionVersionDocument(extensionDocument, new DefaultVersion(version), xcontext).clone(); + + extensionVersionDocument.addXObject(versionObject.clone()); + + // Save if dedicated version page + saveDocument(extensionVersionDocument, "Migrate the extension version", xcontext); + } + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, boolean save) throws XWikiException { @@ -1223,8 +1259,14 @@ private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument updateExtension(extensionVersion, versionObject, xcontext); // Save if dedicated version page - if (save && needSave && this.extensionStore.isVersionPageEnabled(extensiondocument)) { - xcontext.getWiki().saveDocument(extensionVersionDocument, "Update", xcontext); + if (save && needSave) { + boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensiondocument); + if (versionPageEnabled) { + saveDocument(extensionVersionDocument, "Update", xcontext); + + // Since the data are saved, there is no point saving the extension document + return false; + } } return needSave; From a30eb167094aa1c928877a8e388003b7956e5616 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 25 Sep 2025 17:15:17 +0200 Subject: [PATCH 08/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix livedata to sort by desc version and display proper empty value translation --- .../src/main/resources/ExtensionCode/VersionsHome.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml index 70cdbd7aaed4..b0ee263bff14 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml @@ -43,13 +43,16 @@ #set ($optionsLD = { 'className': 'ExtensionCode.ExtensionVersionClass', - 'location': "${locationCriteria}" + 'location': "${locationCriteria}", + 'translationPrefix': 'extension.repository.' }) {{liveData id="versions" properties="version,notes" source="liveTable" + sort="version:desc" + limit="5" sourceParameters="$services.rendering.escape($escapetool.url($optionsLD), 'xwiki/2.1')" }}{ "meta": { From dba94c4dbb6b4b386011bed01fa6cf66be3a853d Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Thu, 25 Sep 2025 17:38:05 +0200 Subject: [PATCH 09/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Provide index for versions --- .../internal/RepositoryManager.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index f625b7e187da..59d85204e7ff 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -657,24 +657,23 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository } // Update features versions - + long index = 0; for (Map.Entry<Version, String> entry : featureVersions.entrySet()) { Version version = entry.getKey(); String id = entry.getValue(); // Give priority to extension version in case of conflict if (!extensionVersions.containsKey(version)) { - updateVersion(id, version, extension, repository, extensionDocument); + updateVersion(id, version, extension, repository, index++, extensionDocument); } } // Update extension versions - for (Map.Entry<Version, String> entry : extensionVersions.entrySet()) { Version version = entry.getKey(); String id = entry.getValue(); - updateVersion(id, version, extension, repository, extensionDocument); + updateVersion(id, version, extension, repository, index++, extensionDocument); } // Save @@ -700,7 +699,7 @@ private void saveDocument(XWikiDocument document, String comment, XWikiContext x } private boolean updateVersion(String id, Version version, Extension extension, ExtensionRepository repository, - XWikiDocument extensionDocument) + long index, XWikiDocument extensionDocument) { try { Extension versionExtension; @@ -713,7 +712,7 @@ private boolean updateVersion(String id, Version version, Extension extension, E } // Update version related informations - return updateExtensionVersion(versionExtension, extensionDocument, true); + return updateExtensionVersion(versionExtension, extensionDocument, index, true); } catch (Exception e) { this.logger.error("Failed to resolve extension with id [" + id + "] and version [" + version + "] on repository [" + repository + "]", e); @@ -773,7 +772,7 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str // FIXME: find a more elegant solution try { XWikiDocument extensionDocumentClone = extensionDocument.clone(); - updateExtensionVersion(extension, extensionDocumentClone, false); + updateExtensionVersion(extension, extensionDocumentClone, 0, false); extensionVersionObject = extensionDocumentClone .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); } catch (XWikiException e) { @@ -1202,8 +1201,8 @@ private void moveLegacyVersion(XWikiDocument extensionDocument, BaseObject versi saveDocument(extensionVersionDocument, "Migrate the extension version", xcontext); } - private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, boolean save) - throws XWikiException + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, + long index, boolean save) throws XWikiException { boolean needSave = false; @@ -1256,7 +1255,10 @@ private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument } // Common properties - updateExtension(extensionVersion, versionObject, xcontext); + needSave |= updateExtension(extensionVersion, versionObject, xcontext); + + // index + needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_INDEX, index); // Save if dedicated version page if (save && needSave) { From 96925b48502c601de96a92e4df8a93bb34910888 Mon Sep 17 00:00:00 2001 From: Mathieu Pace <mathieu.pace@xwiki.com> Date: Thu, 25 Sep 2025 18:20:23 +0300 Subject: [PATCH 10/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * update class --- .../ExtensionCode/ExtensionVersionClass.xml | 315 ++++++++++++ .../ExtensionCode/ExtensionVersionSheet.xml | 457 +++++++++++++++++- .../ExtensionVersionTemplate.xml | 312 ++++++++++++ 3 files changed, 1075 insertions(+), 9 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml index b1dec9d138f1..eb0184ad547f 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml @@ -46,6 +46,99 @@ <defaultWeb/> <nameField/> <validationScript/> + <allowednamespaces> + <cache>0</cache> + <customDisplay/> + <defaultValue/> + <disabled>0</disabled> + <displayType>input</displayType> + <freeText/> + <hint/> + <largeStorage>0</largeStorage> + <multiSelect>1</multiSelect> + <name>allowednamespaces</name> + <number>26</number> + <picker>0</picker> + <prettyName>Allowed namespaces</prettyName> + <relationalStorage>1</relationalStorage> + <separator> </separator> + <separators>|</separators> + <size>1</size> + <sort>none</sort> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <values/> + <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType> + </allowednamespaces> + <allowednamespaces_empty> + <customDisplay/> + <defaultValue>0</defaultValue> + <disabled>0</disabled> + <displayFormType>checkbox</displayFormType> + <displayType/> + <hint/> + <name>allowednamespaces_empty</name> + <number>27</number> + <prettyName>Is allowed namespaces empty</prettyName> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.BooleanClass</classType> + </allowednamespaces_empty> + <authors> + <cache>0</cache> + <customDisplay>{{include reference="ExtensionCode.ExtensionAuthorsDisplayer"/}}</customDisplay> + <defaultValue/> + <disabled>0</disabled> + <displayType>input</displayType> + <freeText/> + <hint/> + <largeStorage>0</largeStorage> + <multiSelect>1</multiSelect> + <name>authors</name> + <number>7</number> + <picker>0</picker> + <prettyName>Authors</prettyName> + <relationalStorage>0</relationalStorage> + <separator> </separator> + <separators>,|</separators> + <size>30</size> + <sort>none</sort> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <values/> + <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType> + </authors> + <category> + <cache>0</cache> + <classname>ExtensionCode.ExtensionCategoryClass</classname> + <customDisplay/> + <defaultValue/> + <disabled>0</disabled> + <displayType>select</displayType> + <freeText/> + <hint/> + <idField>id</idField> + <largeStorage>0</largeStorage> + <multiSelect>0</multiSelect> + <name>category</name> + <number>16</number> + <picker>0</picker> + <prettyName>Category</prettyName> + <relationalStorage>0</relationalStorage> + <separator> </separator> + <separators/> + <size>1</size> + <sort>value</sort> + <sql/> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <valueField>name</valueField> + <classType>com.xpn.xwiki.objects.classes.DBListClass</classType> + </category> <download> <contenttype>PureText</contenttype> <customDisplay/> @@ -99,6 +192,45 @@ <validationRegExp/> <classType>com.xpn.xwiki.objects.classes.StringClass</classType> </id> + <licenseName> + <cache>0</cache> + <customDisplay/> + <defaultValue/> + <disabled>0</disabled> + <displayType>select</displayType> + <freeText/> + <hint/> + <largeStorage>0</largeStorage> + <multiSelect>0</multiSelect> + <name>licenseName</name> + <number>10</number> + <picker>0</picker> + <prettyName>License Name</prettyName> + <relationalStorage>0</relationalStorage> + <separator> </separator> + <separators>,|</separators> + <size>1</size> + <sort>none</sort> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <values>GNU General Public License 1|GNU General Public License 2|GNU General Public License 3|GNU Lesser General Public License 2|GNU Lesser General Public License 2.1|GNU Lesser General Public License 3|Apache License 2.0|BSD license|Modified BSD License|Simplified BSD License|GNU Affero General Public License 3|GNU Free Documentation License 1.1|GNU Free Documentation License 1.2|GNU Free Documentation License 1.3|Educational Community License 1.0|Educational Community License 2.0|Do What The Fuck You Want To Public License 2</values> + <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType> + </licenseName> + <name> + <customDisplay/> + <disabled>0</disabled> + <hint/> + <name>name</name> + <number>3</number> + <picker>0</picker> + <prettyName>Name</prettyName> + <size>30</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.StringClass</classType> + </name> <notes> <contenttype>---</contenttype> <customDisplay/> @@ -116,6 +248,31 @@ <validationRegExp/> <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType> </notes> + <properties> + <cache>0</cache> + <customDisplay/> + <defaultValue/> + <disabled>0</disabled> + <displayType>input</displayType> + <freeText/> + <hint/> + <largeStorage>0</largeStorage> + <multiSelect>1</multiSelect> + <name>properties</name> + <number>17</number> + <picker>0</picker> + <prettyName>Properties</prettyName> + <relationalStorage>1</relationalStorage> + <separator> </separator> + <separators>|</separators> + <size>1</size> + <sort>none</sort> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <values/> + <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType> + </properties> <repositories> <cache>0</cache> <customDisplay/> @@ -138,6 +295,102 @@ <values/> <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType> </repositories> + <scmconnection> + <contenttype>PureText</contenttype> + <customDisplay/> + <disabled>0</disabled> + <editor>---</editor> + <hint/> + <name>scmconnection</name> + <number>25</number> + <picker>0</picker> + <prettyName>Sources connection</prettyName> + <restricted>0</restricted> + <rows>1</rows> + <size>100</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType> + </scmconnection> + <scmdevconnection> + <contenttype>PureText</contenttype> + <customDisplay/> + <disabled>0</disabled> + <editor>---</editor> + <hint/> + <name>scmdevconnection</name> + <number>26</number> + <picker>0</picker> + <prettyName>Sources dev connection</prettyName> + <restricted>0</restricted> + <rows>1</rows> + <size>100</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType> + </scmdevconnection> + <source> + <contenttype>PureText</contenttype> + <customDisplay/> + <disabled>0</disabled> + <editor>---</editor> + <hint/> + <name>source</name> + <number>24</number> + <picker>0</picker> + <prettyName>Source</prettyName> + <restricted>0</restricted> + <rows>1</rows> + <size>100</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType> + </source> + <summary> + <customDisplay/> + <disabled>0</disabled> + <hint/> + <name>summary</name> + <number>4</number> + <picker>0</picker> + <prettyName>Summary</prettyName> + <size>75</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.StringClass</classType> + </summary> + <type> + <cache>0</cache> + <classname>ExtensionCode.ExtensionTypeClass</classname> + <customDisplay/> + <defaultValue/> + <disabled>0</disabled> + <displayType>select</displayType> + <freeText/> + <hint/> + <idField>id</idField> + <largeStorage>0</largeStorage> + <multiSelect>0</multiSelect> + <name>type</name> + <number>2</number> + <picker>0</picker> + <prettyName>Type</prettyName> + <relationalStorage>0</relationalStorage> + <separator> </separator> + <separators/> + <size>1</size> + <sort>value</sort> + <sql/> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <valueField>name</valueField> + <classType>com.xpn.xwiki.objects.classes.DBListClass</classType> + </type> <version> <customDisplay/> <disabled>0</disabled> @@ -152,7 +405,69 @@ <validationRegExp/> <classType>com.xpn.xwiki.objects.classes.StringClass</classType> </version> + <website> + <contenttype>PureText</contenttype> + <customDisplay/> + <disabled>0</disabled> + <editor>---</editor> + <hint/> + <name>website</name> + <number>6</number> + <picker>0</picker> + <prettyName>Website</prettyName> + <restricted>0</restricted> + <rows>1</rows> + <size>100</size> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType> + </website> </class> + <object> + <name>ExtensionCode.ExtensionVersionClass</name> + <number>0</number> + <className>XWiki.ClassSheetBinding</className> + <guid>75bd5971-88a0-48b0-aa01-a817cbe3b4f9</guid> + <class> + <name>XWiki.ClassSheetBinding</name> + <customClass/> + <customMapping/> + <defaultViewSheet/> + <defaultEditSheet/> + <defaultWeb/> + <nameField/> + <validationScript/> + <sheet> + <cache>0</cache> + <classname/> + <customDisplay/> + <disabled>0</disabled> + <displayType>input</displayType> + <hint/> + <idField/> + <multiSelect>0</multiSelect> + <name>sheet</name> + <number>1</number> + <picker>1</picker> + <prettyName>Sheet</prettyName> + <relationalStorage>0</relationalStorage> + <separator> </separator> + <separators/> + <size>30</size> + <sort>none</sort> + <sql/> + <unmodifiable>0</unmodifiable> + <validationMessage/> + <validationRegExp/> + <valueField/> + <classType>com.xpn.xwiki.objects.classes.PageClass</classType> + </sheet> + </class> + <property> + <sheet>ExtensionCode.ExtensionVersionSheet</sheet> + </property> + </object> <object> <name>ExtensionCode.ExtensionVersionClass</name> <number>0</number> diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml index a7e82ceffa6e..cd56f30c1edd 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml @@ -31,19 +31,144 @@ <author>xwiki:XWiki.Admin</author> <contentAuthor>xwiki:XWiki.Admin</contentAuthor> <version>1.1</version> - <title>ExtensionVersion Sheet + #if($doc.getObject('ExtensionCode.ExtensionVersionClass'))$doc.getObject('ExtensionCode.ExtensionVersionClass').getProperty('name').value $doc.getObject('ExtensionCode.ExtensionVersionClass').getProperty('version').value#{else}Extension version sheet#end false xwiki/2.0 true - {{velocity}} -## You can modify this page to customize the presentation of your object. -## At first you should keep the default presentation and just save the document. - -#set($class = $doc.getObject('ExtensionCode.ExtensionVersionClass').xWikiClass) -#foreach($prop in $class.properties) - ; $prop.prettyName - : $doc.display($prop.getName()) + {{include reference="ExtensionCode.RepositoryCode"/}} + +{{template name="extension.vm"/}} + +{{velocity output="false"}} +#if ($xcontext.action == 'edit') + #set($isEditMode = true) +#else + #set($isViewMode = true) +#end + +#set($extension = $doc.getObject("ExtensionCode.ExtensionClass")) +#set($extensionVersion = $doc.getObject("ExtensionCode.ExtensionVersionClass")) +{{/velocity}} + +{{velocity}} +#if ($extensionVersion && !$extension) + $doc.use("ExtensionCode.ExtensionVersionClass") + ##------- Back button ---------------- + [[$services.icon.render('left') Extension page>>$services.model.serialize($doc.getDocumentReference().getParent().getParent().getParent())||class="btn btn-primary"]]## + + #if ($isViewMode) + ##------- Icon & Summary ----------------- + (% class="extensionSummary" %) + **${services.rendering.escape($doc.getValue('summary'), 'xwiki/2.1')}** + + ## Viewing + ## + {{box cssClass="floatinginfobox col-xs-12 col-sm-6 pull-right"}} + (% class="extensionInfo" %) + ##------- Type -------------- + #set($typeDisplay = $extensionVersion.getProperty('type').value) + #if ("$!typeDisplay" == '') + #set($typeDisplay = $type) + #end + |(% class="label" width='30%' %)Type(%%)|$services.rendering.escape($typeDisplay, 'xwiki/2.1') + ##------- Category -------------- + #set($categoryDisplay = $extensionVersion.getProperty('category').value) + #if ("$!categoryDisplay" == '') + #set($categoryDisplay = $category) + #end + #if ($categoryDisplay) + |(% class="label" width='30%' %)Category(%%)|$services.rendering.escape($categoryDisplay, 'xwiki/2.1') + #end + ##------- Developed By -------- + #set($authors = $doc.getValue("authors")) + |(% class="label" %)Developed by(%%)|#if ($authors.isEmpty()) + Unknown + #else + $doc.display('authors') + #end + ##------- Website -------------- + #set($website = $extensionVersion.getProperty("website").value) + #if ("$!website" != '') + |(% class="label" %)Website(%%)|#if ($website.length() > 40) + [[$services.rendering.escape($services.rendering.escape($website.substring(0, 40), 'xwiki/2.1'), 'xwiki/2.1')...>>$services.rendering.escape($website, 'xwiki/2.1')]] + #else + [[$services.rendering.escape($website, 'xwiki/2.1')]] + #end + #end + ##------- License -------- + #set($licenseName = $doc.getValue("licenseName")) + #if ("$!licenseName" != "") + |(% class="label" %)License(%%)|$services.rendering.escape($licenseName, 'xwiki/2.1') + #else + |(% class="label" %)License(%%)|Unknown + #end + ##--------------------------------- + ##------- Sheet extensions -------- + #if (!$sheetExtensions.isEmpty()) + + #foreach($sheetExtension in $sheetExtensions) + $doc.display('view_info', 'view', $sheetExtension) + #end + #end + ##------------------------------------------- + ##------- Extension Manager ----------------- + #if ($doc.getValue('validExtension') == 1) + + {{success}}**Installable with the Extension Manager**{{/success}} + #end + ##------------------------ + (%class="btn-group pull-right"%)((( + ##------- Download button ------ + #set ($version = $extensionVersion.getProperty('version').value) + #set ($download = $extensionVersion.getProperty('download').value) + #if ("$!download" == '') + #if ($doc.getAttachment("${id}-${version}.${type}")) + [[$services.icon.render('download') Download v$services.rendering.escape($services.rendering.escape($version, 'xwiki/2.1'), 'xwiki/2.1')>>$services.rendering.escape("attach:${id}-${version}.${type}")||class="btn btn-primary"]]## + #end + #else + [[$services.icon.render('download') Download v$services.rendering.escape($services.rendering.escape($version, 'xwiki/2.1'), 'xwiki/2.1')>>$services.rendering.escape($download, 'xwiki/2.1')||class="btn btn-primary"]]## + #end + ##------- Source -------- + #set($source = $extensionVersion.getProperty("source").value) + #if ("$!source" != "") + [[Sources>>$services.rendering.escape($source, 'xwiki/2.1')||class="btn btn-default"]]## + #end + ))) + {{/box}} + + {{box cssClass="toc col-xs-12 col-sm-6"}}(% class="label" %)Table of contents(%%)((({{toc/}}))){{/box}} + (%class="clearfix"%)((())) + #end + + ## Release notes + = Release Notes = + #set($releaseNotes = $doc.getValue("notes")) + $doc.display('notes') + + #if ($isViewMode) + #set($extensionDependencies = $doc.getObjects('ExtensionCode.ExtensionDependencyClass', 'extensionVersion', $version)) + #if ($extensionDependencies.size() > 0) + = Dependencies = + + Dependencies for this extension ($services.rendering.escape("${extensionVersion.getValue('id')} ${version}", 'xwiki/2.1')): + #foreach($extensionDependency in $extensionDependencies) + #set($dependencyDocumentName = $null) + #set($dependencyDocumentNames = $services.query.xwql('from doc.object(ExtensionCode.ExtensionClass) as extension where extension.id = :id').bindValue("id", $extensionDependency.id).execute()) + #if (!$dependencyDocumentNames.isEmpty()) + #set($dependencyDocumentName = $dependencyDocumentNames.get(0)) + #end + #if ($dependencyDocumentName) + * [[$services.rendering.escape($services.rendering.escape($extensionDependency.getValue('id'), 'xwiki/2.1'), 'xwiki/2.1')>>$services.rendering.escape($dependencyDocumentName, 'xwiki/2.1')]] $services.rendering.escape($extensionDependency.getValue('constraint'), 'xwiki/2.1') + #else + * $services.rendering.escape("${extensionDependency.getValue('id')} ${extensionDependency.getValue('constraint')}", 'xwiki/2.1') + #end + #end + #end + #end + +#else + This class sheet must be applied on a document containing an ExtensionCode.ExtensionVersionClass object #end {{/velocity}} @@ -60,9 +185,323 @@ + + 0 + defaultEditMode + 1 + Default Edit Mode + 15 + 0 + com.xpn.xwiki.objects.classes.StringClass + + + ExtensionCode.ExtensionVersionSheet + 0 + XWiki.StyleSheetExtension + 6a915691-5ff6-4850-98ab-917595c2fc10 + + XWiki.StyleSheetExtension + + + + + + + + + 0 + long + 0 + select + forbidden + 0 + 0 + cache + 5 + Caching policy + 0 + + |, + 1 + 0 + long|short|default|forbid + com.xpn.xwiki.objects.classes.StaticListClass + + + PureText + 0 + PureText + code + 2 + Code + 0 + 20 + 50 + 0 + com.xpn.xwiki.objects.classes.TextAreaClass + + + 0 + 0 + select + forbidden + 0 + 0 + contentType + 6 + Content Type + 0 + + |, + 1 + 0 + CSS|LESS + com.xpn.xwiki.objects.classes.StaticListClass + + + 0 + name + 1 + Name + 30 + 0 + com.xpn.xwiki.objects.classes.StringClass + + + 0 + select + yesno + parse + 4 + Parse content + 0 + com.xpn.xwiki.objects.classes.BooleanClass + + + 0 + 0 + select + forbidden + 0 + 0 + use + 3 + Use this extension + 0 + + |, + 1 + 0 + currentPage|onDemand|always + com.xpn.xwiki.objects.classes.StaticListClass + + + + long + + + #template('colorThemeInit.vm') + +.extensionSummary { + background-color: $theme.backgroundSecondaryColor; + border: 1px dotted $theme.borderColor; + padding: 5px; + display: block; +} + +.main .extensionInfo { + margin: 0; +} + +.extensionInfo .label { + font-size: 0.85em; + font-weight: bold; + text-transform: uppercase; +} + +/*Rating*/ +.extensionInfo .rating-wrapper { + float: left; +} + +.extensionInfo .rating-container > div { + float: left; + margin-right: 10px +} + + + CSS + + + Extension CSS + + + 1 + + + always + + + + ExtensionCode.ExtensionVersionSheet + 1 + XWiki.StyleSheetExtension + ac3e481a-5ec9-41eb-9a4d-9b109bcae4be + + XWiki.StyleSheetExtension + + + + + + + + + 0 + long + 0 + select + forbidden + 0 + 0 + cache + 5 + Caching policy + 0 + + |, + 1 + 0 + long|short|default|forbid + com.xpn.xwiki.objects.classes.StaticListClass + + + PureText + 0 + PureText + code + 2 + Code + 0 + 20 + 50 + 0 + com.xpn.xwiki.objects.classes.TextAreaClass + + + 0 + 0 + select + forbidden + 0 + 0 + contentType + 6 + Content Type + 0 + + |, + 1 + 0 + CSS|LESS + com.xpn.xwiki.objects.classes.StaticListClass + + + 0 + name + 1 + Name + 30 + 0 + com.xpn.xwiki.objects.classes.StringClass + + + 0 + select + yesno + parse + 4 + Parse content + 0 + com.xpn.xwiki.objects.classes.BooleanClass + + + 0 + 0 + select + forbidden + 0 + 0 + use + 3 + Use this extension + 0 + + |, + 1 + 0 + currentPage|onDemand|always + com.xpn.xwiki.objects.classes.StaticListClass + + + + long + + + .extensionSummary td, /* PROBLEM: General styling reset */ +.extensionInfo td { + border: 0; + padding-top: 0; + padding-bottom: 0; +} + +@media (min-width: 768px) { + .box.floatinginfobox, .box.toc { + max-width: 49%; + } +} + +.box .label { /* PROBLEM: Usage of a class with meaning inside Bootstrap */ + color: inherit; + text-align: inherit; + display: table-cell; +} + +.toc .label { + margin: 0; +} + +/* Download and Source button alignment */ +.box > p { + margin-bottom: 0; +} + +/* Spacing for repository info message */ +.box .extensionInfo { + margin-top: 10px; +} + +/* XINFRA-134: Aligning table cell text */ +.floatinginfobox td { + vertical-align: baseline; +} + + + CSS + + + Junco + + + + + + currentPage + + diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionTemplate.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionTemplate.xml index 1117e93abdff..f611b6a0a5ac 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionTemplate.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionTemplate.xml @@ -51,6 +51,99 @@ + + 0 + + + 0 + input + + + 0 + 1 + allowednamespaces + 26 + 0 + Allowed namespaces + 1 + + | + 1 + none + 0 + + + + com.xpn.xwiki.objects.classes.StaticListClass + + + + 0 + 0 + checkbox + + + allowednamespaces_empty + 27 + Is allowed namespaces empty + 0 + + + com.xpn.xwiki.objects.classes.BooleanClass + + + 0 + {{include reference="ExtensionCode.ExtensionAuthorsDisplayer"/}} + + 0 + input + + + 0 + 1 + authors + 7 + 0 + Authors + 0 + + ,| + 30 + none + 0 + + + + com.xpn.xwiki.objects.classes.StaticListClass + + + 0 + ExtensionCode.ExtensionCategoryClass + + + 0 + select + + + id + 0 + 0 + category + 16 + 0 + Category + 0 + + + 1 + value + + 0 + + + name + com.xpn.xwiki.objects.classes.DBListClass + PureText @@ -104,11 +197,51 @@ com.xpn.xwiki.objects.classes.StringClass + + 0 + + + 0 + select + + + 0 + 0 + licenseName + 10 + 0 + License Name + 0 + + ,| + 1 + none + 0 + + + GNU General Public License 1|GNU General Public License 2|GNU General Public License 3|GNU Lesser General Public License 2|GNU Lesser General Public License 2.1|GNU Lesser General Public License 3|Apache License 2.0|BSD license|Modified BSD License|Simplified BSD License|GNU Affero General Public License 3|GNU Free Documentation License 1.1|GNU Free Documentation License 1.2|GNU Free Documentation License 1.3|Educational Community License 1.0|Educational Community License 2.0|Do What The Fuck You Want To Public License 2 + com.xpn.xwiki.objects.classes.StaticListClass + + + + 0 + + name + 3 + 0 + Name + 30 + 0 + + + com.xpn.xwiki.objects.classes.StringClass + --- 0 --- + notes 1 0 @@ -120,11 +253,37 @@ com.xpn.xwiki.objects.classes.TextAreaClass + + 0 + + + 0 + input + + + 0 + 1 + properties + 17 + 0 + Properties + 1 + + | + 1 + none + 0 + + + + com.xpn.xwiki.objects.classes.StaticListClass + 0 0 input + 1 repositories 6 @@ -141,6 +300,102 @@ com.xpn.xwiki.objects.classes.StaticListClass + + PureText + + 0 + --- + + scmconnection + 25 + 0 + Sources connection + 0 + 1 + 100 + 0 + + + com.xpn.xwiki.objects.classes.TextAreaClass + + + PureText + + 0 + --- + + scmdevconnection + 26 + 0 + Sources dev connection + 0 + 1 + 100 + 0 + + + com.xpn.xwiki.objects.classes.TextAreaClass + + + PureText + + 0 + --- + + source + 24 + 0 + Source + 0 + 1 + 100 + 0 + + + com.xpn.xwiki.objects.classes.TextAreaClass + + + + 0 + + summary + 4 + 0 + Summary + 75 + 0 + + + com.xpn.xwiki.objects.classes.StringClass + + + 0 + ExtensionCode.ExtensionTypeClass + + + 0 + select + + + id + 0 + 0 + type + 2 + 0 + Type + 0 + + + 1 + value + + 0 + + + name + com.xpn.xwiki.objects.classes.DBListClass + 0 @@ -155,7 +410,37 @@ com.xpn.xwiki.objects.classes.StringClass + + PureText + + 0 + --- + + website + 6 + 0 + Website + 0 + 1 + 100 + 0 + + + com.xpn.xwiki.objects.classes.TextAreaClass + + + + + + + + + + + + + @@ -165,14 +450,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From fb7580d7a402c3f0e2f67059ee7a42ec1bded982 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 25 Sep 2025 17:50:45 +0200 Subject: [PATCH 11/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix missing parent in VersionsHome * Provide index in ExtensionVersionClass --- .../ExtensionCode/ExtensionVersionClass.xml | 26 +++++++++++++++++-- .../resources/ExtensionCode/VersionsHome.xml | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml index eb0184ad547f..7868b0d86b9f 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionClass.xml @@ -20,7 +20,7 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. --> - + ExtensionCode ExtensionVersionClass @@ -149,6 +149,7 @@ 2 0 Download URL + 0 1 100 0 @@ -159,9 +160,12 @@ 0 + 0 input + + 0 1 features 5 @@ -192,6 +196,20 @@ com.xpn.xwiki.objects.classes.StringClass + + + 0 + + index + 20 + long + index + 30 + 0 + + + com.xpn.xwiki.objects.classes.NumberClass + 0 @@ -241,6 +259,7 @@ 1 0 Release Notes + 0 15 75 0 @@ -276,9 +295,12 @@ 0 + 0 input + + 0 1 repositories 6 @@ -495,10 +517,10 @@ 1 1 Sheet - 30 0 + 30 none 0 diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml index b0ee263bff14..a42d633cf294 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/VersionsHome.xml @@ -32,6 +32,7 @@ 1.1 <comment/> + <parent>WebHome</parent> <minorEdit>false</minorEdit> <syntaxId>xwiki/2.1</syntaxId> <hidden>true</hidden> From 446a767001e9e310fd2f9d1323fa8a379ce9dcc8 Mon Sep 17 00:00:00 2001 From: Thomas Mortagne <thomas.mortagne@gmail.com> Date: Thu, 25 Sep 2025 19:22:14 +0300 Subject: [PATCH 12/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * fix validation --- .../repository/internal/ExtensionStore.java | 24 ++++ .../internal/ExtensionUpdaterListener.java | 7 +- .../internal/RepositoryManager.java | 131 +++++++++++------- 3 files changed, 111 insertions(+), 51 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 1e130e8242e3..f832a06d053a 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -383,6 +383,7 @@ public BaseObject getExtensionSupporterObject(XWikiDocument extensionSupporterDo /** * Retrieve the extension version document for the given extension document and the given version. + * * @param extensionDocument the document of the extension * @param extensionVersion the version for which to retrieve the document * @param xcontext the current context @@ -398,6 +399,7 @@ public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument /** * Retrieve the extension version document for the given extension document and the given version. + * * @param extensionDocument the document of the extension * @param extensionVersion the version for which to retrieve the document * @param xcontext the current context @@ -421,6 +423,7 @@ public XWikiDocument getExtensionVersionDocument(XWikiDocument extensionDocument /** * Retrieve the xobject of the version for given extension. + * * @param extensionVersionDocument the document that might contain the version object * @param extensionVersion the version for which to retrieve the object * @param create {@code true} to create the object if it doesn't exist @@ -448,6 +451,7 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionVersionDocume /** * Retrieve the xobject of the version from the given document. + * * @param document the document where to retrieve the version object. * @param version the version for which to retrieve the object * @return the version object or {@code null} if it doesn't exist and shouldn't be created @@ -460,6 +464,7 @@ public BaseObject getExtensionVersionObject(XWikiDocument document, String versi /** * Retrieve the xobject of the version from the given document. + * * @param document the document where to retrieve the version object. * @param version the version for which to retrieve the object * @return the version object or {@code null} if it doesn't exist and shouldn't be created @@ -529,6 +534,25 @@ public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws : null; } + public String getLastVersion(String extensionId) throws QueryException + { + Query query = + this.queryManager.createQuery("select version.version, version.index from Document doc, doc.object(" + + XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME + + ") as version where version.id = :extensionId order by version.index desc", Query.XWQL); + + query.bindValue("extensionId", extensionId); + query.setLimit(0); + + List<Object[]> results = query.execute(); + + if (results.isEmpty()) { + return null; + } + + return (String) results.get(0)[0]; + } + public BaseObject getExtensionObject(XWikiDocument extensionDocument) { return extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionUpdaterListener.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionUpdaterListener.java index b99d4228c028..7a997a467f36 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionUpdaterListener.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionUpdaterListener.java @@ -34,7 +34,6 @@ import org.xwiki.observation.EventListener; import org.xwiki.observation.event.Event; -import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; @@ -46,8 +45,8 @@ public class ExtensionUpdaterListener implements EventListener /** * Listened events. */ - private static final List<Event> EVENTS = Arrays.<Event> asList(new DocumentCreatingEvent(), - new DocumentUpdatingEvent()); + private static final List<Event> EVENTS = + Arrays.<Event>asList(new DocumentCreatingEvent(), new DocumentUpdatingEvent()); /** * The logger to log. @@ -80,7 +79,7 @@ public void onEvent(Event event, Object source, Object data) if (extensionObject != null) { try { this.repositoryManagerProvider.get().validateExtension(document, false); - } catch (XWikiException e) { + } catch (Exception e) { this.logger.error("Failed to validate extension in document [{}]", document.getDocumentReference(), e); } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index 59d85204e7ff..fae929796c37 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -157,7 +157,7 @@ public class RepositoryManager private int maxStringPropertySize = -1; - public void validateExtension(XWikiDocument document, boolean save) throws XWikiException + public void validateExtension(XWikiDocument document, boolean save) throws XWikiException, QueryException { BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); @@ -215,7 +215,6 @@ public void validateExtension(XWikiDocument document, boolean save) throws XWiki } // Save document - if (save && needSave) { xcontext.getWiki().saveDocument(document, "Validated extension", true, xcontext); } @@ -226,38 +225,43 @@ public void validateExtension(XWikiDocument document, boolean save) throws XWiki * * @param document the extension document * @return the last version + * @throws QueryException when failing to resolve the last version */ - private String findLastVersion(XWikiDocument document) + private String findLastVersion(XWikiDocument document) throws QueryException { String extensionId = getExtensionId(document); - DocumentReference versionClassReference = - getClassReference(document, XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); - - List<BaseObject> versionObjects = document.getXObjects(versionClassReference); - - DefaultVersion lastVersion = null; - if (versionObjects != null) { - for (BaseObject versionObject : versionObjects) { - if (versionObject != null) { - String versionId = - this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_ID); - - if (StringUtils.isEmpty(versionId) || Objects.equals(extensionId, versionId)) { - String versionString = - this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION); - if (versionString != null) { - DefaultVersion version = new DefaultVersion(versionString); - if (lastVersion == null || version.compareTo(lastVersion) > 0) { - lastVersion = version; + if (this.extensionStore.isVersionPageEnabled(document)) { + return this.extensionStore.getLastVersion(extensionId); + } else { + DocumentReference versionClassReference = + getClassReference(document, XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + + List<BaseObject> versionObjects = document.getXObjects(versionClassReference); + + DefaultVersion lastVersion = null; + if (versionObjects != null) { + for (BaseObject versionObject : versionObjects) { + if (versionObject != null) { + String versionId = + this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_ID); + + if (StringUtils.isEmpty(versionId) || Objects.equals(extensionId, versionId)) { + String versionString = + this.extensionStore.getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION); + if (versionString != null) { + DefaultVersion version = new DefaultVersion(versionString); + if (lastVersion == null || version.compareTo(lastVersion) > 0) { + lastVersion = version; + } } } } } } - } - return lastVersion != null ? lastVersion.getValue() : null; + return lastVersion != null ? lastVersion.getValue() : null; + } } private String getExtensionId(XWikiDocument document) @@ -271,13 +275,13 @@ private DocumentReference getClassReference(XWikiDocument document, EntityRefere } /** - * @param document the extension document + * @param extensionDocument the extension document * @param extensionObject the extension object * @param xcontext the XWiki context * @return true if the extension is valid from Extension Manager point of view * @throws XWikiException unknown issue when manipulating the model */ - private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWikiContext xcontext) + private boolean isValid(XWikiDocument extensionDocument, BaseObject extensionObject, XWikiContext xcontext) throws XWikiException { String extensionId = this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID); @@ -289,14 +293,15 @@ private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWik valid = this.configuration.isValidType(type); if (valid) { - // Versions valid = false; + + // Legacy Versions List<BaseObject> extensionVersions = - document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); if (extensionVersions != null) { for (BaseObject extensionVersionObject : extensionVersions) { if (extensionVersionObject != null) { - valid = isVersionValid(document, type, extensionVersionObject, xcontext); + valid = isVersionValid(extensionDocument, type, extensionVersionObject, xcontext); if (!valid) { return false; @@ -304,6 +309,20 @@ private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWik } } } + + // New version storage + if (this.extensionStore.isVersionPageEnabled(extensionObject)) { + // Current version + String lastVersion = + extensionDocument.getStringValue(XWikiRepositoryModel.PROP_EXTENSION_LASTVERSION); + if (StringUtils.isNotBlank(lastVersion)) { + valid = isVersionValid(extensionDocument, type, lastVersion, xcontext); + + if (!valid) { + return false; + } + } + } } } @@ -316,6 +335,8 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e // Has a version String extensionVersion = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION); + + // Has a version if (StringUtils.isBlank(extensionVersion)) { this.logger.debug("No actual version provided for object [{}({})]", XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber()); @@ -323,6 +344,19 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e return false; } + return isVersionValid(document, type, extensionVersion, xcontext); + } + + private boolean isVersionValid(XWikiDocument document, String type, String extensionVersion, XWikiContext xcontext) + { + // Has a version + if (StringUtils.isBlank(extensionVersion)) { + this.logger.debug("No actual version provided for document [{}({})]", + XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, document.getDocumentReference()); + + return false; + } + boolean valid; if (StringUtils.isEmpty(type)) { @@ -332,9 +366,9 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e ResourceReference resourceReference = null; try { resourceReference = getDownloadReference(document, extensionVersion); - } catch (ResolveException e) { - logger.debug("Cannot obtain download source reference for object [{}({})]", - XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber()); + } catch (Exception e) { + logger.debug("Cannot obtain download source reference for version [({})]", extensionVersion); + return false; } @@ -373,8 +407,7 @@ private boolean isVersionValid(XWikiDocument document, String type, BaseObject e } else { valid = false; - this.logger.debug("No actual download provided for object [{}({})]", - XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber()); + this.logger.debug("No actual download provided for version [({})]", extensionVersion); } } @@ -395,11 +428,12 @@ public void validateExtensions() throws QueryException, XWikiException * @since 42 */ public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion) - throws ResolveException + throws ResolveException, XWikiException { String downloadURL = null; - BaseObject extensionVersionObject = getExtensionVersionObject(document, extensionVersion, false); + BaseObject extensionVersionObject = + getExtensionVersionObject(document, extensionVersion, false, this.xcontextProvider.get()); if (extensionVersionObject != null) { downloadURL = this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD); @@ -727,18 +761,21 @@ private boolean updateVersion(String id, Version version, Extension extension, E public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, XWikiContext xcontext) throws XWikiException { - return getExtensionVersionObject( - this.extensionStore.getExtensionVersionDocument(extensionDocument, version, xcontext), version, true); + return getExtensionVersionObject(extensionDocument, version, true, xcontext); } /** * @since 42 */ - public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying) + public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying, + XWikiContext xcontext) throws XWikiException { + XWikiDocument extensionVersionDocument = + this.extensionStore.getExtensionVersionDocument(extensionDocument, version, xcontext); + if (version == null) { List<BaseObject> objects = - extensionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); + extensionVersionDocument.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE); if (objects == null || objects.isEmpty()) { return null; @@ -747,18 +784,18 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str } } - BaseObject extensionVersionObject = extensionDocument + BaseObject extensionVersionObject = extensionVersionDocument .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); if (extensionVersionObject == null && allowProxying - && this.extensionStore.isVersionProxyingEnabled(extensionDocument)) { + && this.extensionStore.isVersionProxyingEnabled(extensionVersionDocument)) { // No ExtensionVersionClass object for the version, but proxy is enabled, so try to find remotely Extension extension = null; try { - extension = resolveExtensionVersion(extensionDocument, version); + extension = resolveExtensionVersion(extensionVersionDocument, version); } catch (ExtensionNotFoundException e) { this.logger.debug("No extension could be found remotely with version [{}] for extension page [{}]", - version, extensionDocument.getDocumentReference()); + version, extensionVersionDocument.getDocumentReference()); } catch (ResolveException e) { throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -771,7 +808,7 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str // Create a "detached" xobject for that extension version // FIXME: find a more elegant solution try { - XWikiDocument extensionDocumentClone = extensionDocument.clone(); + XWikiDocument extensionDocumentClone = extensionVersionDocument.clone(); updateExtensionVersion(extension, extensionDocumentClone, 0, false); extensionVersionObject = extensionDocumentClone .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); @@ -1201,8 +1238,8 @@ private void moveLegacyVersion(XWikiDocument extensionDocument, BaseObject versi saveDocument(extensionVersionDocument, "Migrate the extension version", xcontext); } - private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, - long index, boolean save) throws XWikiException + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, long index, + boolean save) throws XWikiException { boolean needSave = false; From 5a33d738467cbc48c6c6cd33674a7c737447c2b6 Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Fri, 26 Sep 2025 08:02:49 +0200 Subject: [PATCH 13/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix type in the version sheet --- .../ExtensionCode/ExtensionVersionSheet.xml | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml index cd56f30c1edd..5eea88705054 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml @@ -20,7 +20,7 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. --> -<xwikidoc version="1.5" reference="ExtensionCode.ExtensionVersionSheet" locale=""> +<xwikidoc version="1.6" reference="ExtensionCode.ExtensionVersionSheet" locale=""> <web>ExtensionCode</web> <name>ExtensionVersionSheet</name> <language/> @@ -53,6 +53,13 @@ {{velocity}} #if ($extensionVersion && !$extension) + #set($type = $extensionVersion.getProperty('type').value) + #set ($extensionTypeDocumentNames = $services.query.xwql('from doc.object(ExtensionCode.ExtensionTypeClass) as type where type.id = :id').bindValue("id", $type).execute()) + #if ($extensionTypeDocumentNames.size() > 0) + #set ($extensionTypeDocumentName = $extensionTypeDocumentNames.get(0)) + #set($extensionTypeObject = $xwiki.getDocument($extensionTypeDocumentName).getObject("ExtensionCode.ExtensionTypeClass")) + #end + $doc.use("ExtensionCode.ExtensionVersionClass") ##------- Back button ---------------- [[$services.icon.render('left') Extension page>>$services.model.serialize($doc.getDocumentReference().getParent().getParent().getParent())||class="btn btn-primary"]]## @@ -67,7 +74,7 @@ {{box cssClass="floatinginfobox col-xs-12 col-sm-6 pull-right"}} (% class="extensionInfo" %) ##------- Type -------------- - #set($typeDisplay = $extensionVersion.getProperty('type').value) + #set($typeDisplay = $extensionTypeObject.getProperty('name').value) #if ("$!typeDisplay" == '') #set($typeDisplay = $type) #end @@ -185,15 +192,6 @@ <defaultWeb/> <nameField/> <validationScript/> - <defaultEditMode> - <disabled>0</disabled> - <name>defaultEditMode</name> - <number>1</number> - <prettyName>Default Edit Mode</prettyName> - <size>15</size> - <unmodifiable>0</unmodifiable> - <classType>com.xpn.xwiki.objects.classes.StringClass</classType> - </defaultEditMode> </class> <property> <defaultEditMode/> From 6316ec3f731a11a988c62c1a24ca9b70af83697a Mon Sep 17 00:00:00 2001 From: Simon Urli <simon.urli@xwiki.com> Date: Fri, 26 Sep 2025 08:07:58 +0200 Subject: [PATCH 14/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix the syntax for ExtensionVersionSheet --- .../src/main/resources/ExtensionCode/ExtensionVersionSheet.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml index 5eea88705054..58004ad2c04e 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionVersionSheet.xml @@ -34,7 +34,7 @@ <title>#if($doc.getObject('ExtensionCode.ExtensionVersionClass'))$doc.getObject('ExtensionCode.ExtensionVersionClass').getProperty('name').value $doc.getObject('ExtensionCode.ExtensionVersionClass').getProperty('version').value#{else}Extension version sheet#end false - xwiki/2.0 + xwiki/2.1 true {{include reference="ExtensionCode.RepositoryCode"/}} From bf45bbbaa4ac710e34bceac574a538ab46687fba Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Fri, 26 Sep 2025 08:46:26 +0200 Subject: [PATCH 15/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix the ExtensionSheet to have proper checks for displaying versions --- .../ExtensionCode/ExtensionSheet.xml | 74 +++++++++---------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml index 41108ef3210d..37324637d19d 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml @@ -303,49 +303,47 @@ #end ## Only display release notes if there are downloads and release notes - #if ($lastVersionObject) - #if ($doc.getValue('versionPage') == 1) - #set ($versionPageRef = $services.model.createEntityReference('Versions', 'page', $doc.pageReference)) - = Versions = - - {{include page="$services.rendering.escape($services.model.serialize($versionPageRef), 'xwiki/2.1')"/}} - #else - #set ($releaseNotes = []) - #set ($versions = $doc.getObjects("ExtensionCode.ExtensionVersionClass")) - #foreach ($versionObject in $versions) - #set ($notes = $!{versionObject.display('notes', 'read')}) - #set ($version = $!{versionObject.getProperty('version').value}) - #if ("$!notes" != '' && "$!version" != '') - #set ($discard = $releaseNotes.add([$version, $notes])) - #end + #if ($doc.getValue('versionPage') == 1) + #set ($versionPageRef = $services.model.createEntityReference('Versions', 'page', $doc.pageReference)) + = Versions = + + {{include page="$services.rendering.escape($services.model.serialize($versionPageRef), 'xwiki/2.1')"/}} + #elseif ($lastVersionObject) + #set ($releaseNotes = []) + #set ($versions = $doc.getObjects("ExtensionCode.ExtensionVersionClass")) + #foreach ($versionObject in $versions) + #set ($notes = $!{versionObject.display('notes', 'read')}) + #set ($version = $!{versionObject.getProperty('version').value}) + #if ("$!notes" != '' && "$!version" != '') + #set ($discard = $releaseNotes.add([$version, $notes])) #end - ## Reverse the list to have latest versions first - #set ($discard = $collectiontool.reverseModifiable($releaseNotes)) - #if (!$releaseNotes.isEmpty()) - = Release Notes = - #foreach ($entry in $releaseNotes) - == v$services.rendering.escape($entry.get(0), 'xwiki/2.1') == - $entry.get(1) - #end + #end + ## Reverse the list to have latest versions first + #set ($discard = $collectiontool.reverseModifiable($releaseNotes)) + #if (!$releaseNotes.isEmpty()) + = Release Notes = + #foreach ($entry in $releaseNotes) + == v$services.rendering.escape($entry.get(0), 'xwiki/2.1') == + $entry.get(1) #end #end + #end - #set($extensionDependencies = $doc.getObjects('ExtensionCode.ExtensionDependencyClass', 'extensionVersion', $lastVersionObject.getValue('version'))) - #if ($extensionDependencies.size() > 0) - = Dependencies = + #set($extensionDependencies = $doc.getObjects('ExtensionCode.ExtensionDependencyClass', 'extensionVersion', $lastVersionObject.getValue('version'))) + #if ($extensionDependencies.size() > 0) + = Dependencies = - Dependencies for this extension ($services.rendering.escape("${extension.getValue('id')} ${doc.getValue('lastVersion')}", 'xwiki/2.1')): - #foreach($extensionDependency in $extensionDependencies) - #set($dependencyDocumentName = $null) - #set($dependencyDocumentNames = $services.query.xwql('from doc.object(ExtensionCode.ExtensionClass) as extension where extension.id = :id').bindValue("id", $extensionDependency.id).execute()) - #if (!$dependencyDocumentNames.isEmpty()) - #set($dependencyDocumentName = $dependencyDocumentNames.get(0)) - #end - #if ($dependencyDocumentName) - * [[$services.rendering.escape($services.rendering.escape($extensionDependency.getValue('id'), 'xwiki/2.1'), 'xwiki/2.1')>>$services.rendering.escape($dependencyDocumentName, 'xwiki/2.1')]] $services.rendering.escape($extensionDependency.getValue('constraint'), 'xwiki/2.1') - #else - * $services.rendering.escape("${extensionDependency.getValue('id')} ${extensionDependency.getValue('constraint')}", 'xwiki/2.1') - #end + Dependencies for this extension ($services.rendering.escape("${extension.getValue('id')} ${doc.getValue('lastVersion')}", 'xwiki/2.1')): + #foreach($extensionDependency in $extensionDependencies) + #set($dependencyDocumentName = $null) + #set($dependencyDocumentNames = $services.query.xwql('from doc.object(ExtensionCode.ExtensionClass) as extension where extension.id = :id').bindValue("id", $extensionDependency.id).execute()) + #if (!$dependencyDocumentNames.isEmpty()) + #set($dependencyDocumentName = $dependencyDocumentNames.get(0)) + #end + #if ($dependencyDocumentName) + * [[$services.rendering.escape($services.rendering.escape($extensionDependency.getValue('id'), 'xwiki/2.1'), 'xwiki/2.1')>>$services.rendering.escape($dependencyDocumentName, 'xwiki/2.1')]] $services.rendering.escape($extensionDependency.getValue('constraint'), 'xwiki/2.1') + #else + * $services.rendering.escape("${extensionDependency.getValue('id')} ${extensionDependency.getValue('constraint')}", 'xwiki/2.1') #end #end #end From 15571745d6e3ce64747fffe56f85f512ce5b7994 Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Fri, 26 Sep 2025 10:08:58 +0300 Subject: [PATCH 16/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * fix regression * add Versions home page --- .../internal/RepositoryManager.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index fae929796c37..1a51cfe3e3a1 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -74,6 +74,7 @@ import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.PageReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.query.Query; import org.xwiki.query.QueryException; @@ -83,6 +84,7 @@ import org.xwiki.rendering.listener.reference.ResourceType; import org.xwiki.rendering.parser.ResourceReferenceParser; import org.xwiki.rendering.renderer.reference.ResourceReferenceTypeSerializer; +import org.xwiki.rendering.syntax.Syntax; import org.xwiki.repository.internal.reference.ExtensionResourceReference; import com.xpn.xwiki.XWikiContext; @@ -1298,19 +1300,38 @@ private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_INDEX, index); // Save if dedicated version page - if (save && needSave) { - boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensiondocument); - if (versionPageEnabled) { - saveDocument(extensionVersionDocument, "Update", xcontext); + if (save) { + // Make sure the Versions home page exist + updateVersionHome(extensiondocument, xcontext); - // Since the data are saved, there is no point saving the extension document - return false; + if (needSave) { + boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensiondocument); + if (versionPageEnabled) { + // Save the version + saveDocument(extensionVersionDocument, "Update", xcontext); + + // Since the data are saved, there is no point saving the extension document + return false; + } } } return needSave; } + private void updateVersionHome(XWikiDocument extensiondocument, XWikiContext xcontext) throws XWikiException + { + PageReference versionsReference = new PageReference("Versions", extensiondocument.getPageReference()); + + if (!xcontext.getWiki().exists(versionsReference, xcontext)) { + XWikiDocument versionsDocument = xcontext.getWiki().getDocument(versionsReference, xcontext); + + versionsDocument.setContent("{{include reference=\"ExtensionCode.VersionsHome\"/}}", Syntax.XWIKI_2_1); + + xcontext.getWiki().saveDocument(versionsDocument, xcontext); + } + } + private String getDownloadURL(Extension extension) { ExtensionResourceReference resource = new ExtensionResourceReference(extension.getId().getId(), @@ -1354,8 +1375,10 @@ protected boolean updateProperties(BaseObject object, Extension extension) throw protected boolean update(BaseObject object, String fieldName, Object value) throws XWikiException { // Make sure collection are lists - if (value instanceof List list) { - value = new ArrayList<>(list); + if (value instanceof Collection) { + if (!(value instanceof List)) { + value = new ArrayList<>((Collection) value); + } } if (ObjectUtils.notEqual(value, this.extensionStore.getValue(object, fieldName))) { From c300862ab0aebbc42344ceec460915d7ef023418 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Fri, 26 Sep 2025 09:31:44 +0200 Subject: [PATCH 17/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Provide a script service for getting the extension version xobject * Fix the download button in ExtensionSheet --- .../script/RepositoryScriptService.java | 21 +++++++++++++++++++ .../ExtensionCode/ExtensionSheet.xml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java index 55e78e4304da..203c11bafbe3 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java @@ -23,6 +23,7 @@ import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; import javax.inject.Singleton; import org.xwiki.component.annotation.Component; @@ -33,10 +34,16 @@ import org.xwiki.extension.repository.ExtensionRepositoryManager; import org.xwiki.extension.version.Version; import org.xwiki.model.reference.DocumentReference; +import org.xwiki.query.QueryException; import org.xwiki.repository.internal.ExtensionStore; import org.xwiki.repository.internal.RepositoryManager; import org.xwiki.script.service.ScriptService; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.api.Object; +import com.xpn.xwiki.doc.XWikiDocument; + @Component @Named("repository") @Singleton @@ -62,6 +69,9 @@ public class RepositoryScriptService implements ScriptService @Inject private Execution execution; + @Inject + private Provider contextProvider; + /** * Store a caught exception in the context, so that it can be later retrieved using {@link #getLastError()}. * @@ -122,4 +132,15 @@ public ExtensionSupportPlans resolveExtensionSupportPlans(Collection sup { return this.extensionStore.resolveExtensionSupportPlans(supportPlanIds); } + + public Object getVersionObject(String extensionId, String version) throws QueryException, XWikiException + { + XWikiDocument extensionDoc = this.extensionStore.getExistingExtensionDocumentById(extensionId); + XWikiContext context = contextProvider.get(); + // FIXME: add some checks + XWikiDocument extensionVersionDocument = + this.extensionStore.getExtensionVersionDocument(extensionDoc, version, context); + return new Object(this.extensionStore.getExtensionVersionObject(extensionVersionDocument, version), + context); + } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml index 37324637d19d..3d82840dea68 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/main/resources/ExtensionCode/ExtensionSheet.xml @@ -176,7 +176,7 @@ ##------- Download button ------ #set($lastVersion = $doc.getValue("lastVersion")) #if ("$!lastVersion" != '') - #set ($lastVersionObject = $doc.getObject("ExtensionCode.ExtensionVersionClass", 'version', $lastVersion)) + #set ($lastVersionObject = $services.repository.getVersionObject($extension.getProperty('id').value, $lastVersion)) #set ($version = $lastVersionObject.getProperty('version').value) #set ($download = $lastVersionObject.getProperty("download").value) #if ("$!download" == '') From c3b27228a245a108fc8b361669e7353681180d7b Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Fri, 26 Sep 2025 10:34:56 +0300 Subject: [PATCH 18/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * fix versions author --- .../java/org/xwiki/repository/internal/RepositoryManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index 1a51cfe3e3a1..bcbd7f7ad954 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -1328,7 +1328,7 @@ private void updateVersionHome(XWikiDocument extensiondocument, XWikiContext xco versionsDocument.setContent("{{include reference=\"ExtensionCode.VersionsHome\"/}}", Syntax.XWIKI_2_1); - xcontext.getWiki().saveDocument(versionsDocument, xcontext); + saveDocument(versionsDocument, "", xcontext); } } From d8fdd02b68e9cd535c560cf17b0b91b514f8d776 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Fri, 26 Sep 2025 10:04:51 +0200 Subject: [PATCH 19/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix index problem when it's not defined in hsqldb * Optimize limit --- .../java/org/xwiki/repository/internal/ExtensionStore.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index f832a06d053a..5491a669672e 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -539,10 +539,11 @@ public String getLastVersion(String extensionId) throws QueryException Query query = this.queryManager.createQuery("select version.version, version.index from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME - + ") as version where version.id = :extensionId order by version.index desc", Query.XWQL); + + ") as version where version.id = :extensionId and version.index is not null " + + "order by version.index desc", Query.XWQL); query.bindValue("extensionId", extensionId); - query.setLimit(0); + query.setLimit(1); List results = query.execute(); From 00d79c77becbc973aaf6628b633e40b70f6fa41d Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Fri, 26 Sep 2025 10:18:43 +0200 Subject: [PATCH 20/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * Fix test --- .../repository/server/ui/ExtensionSheetPageTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/test/java/org/xwiki/repository/server/ui/ExtensionSheetPageTest.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/test/java/org/xwiki/repository/server/ui/ExtensionSheetPageTest.java index b1dc67cbb8f3..112fa302f851 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/test/java/org/xwiki/repository/server/ui/ExtensionSheetPageTest.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-ui/src/test/java/org/xwiki/repository/server/ui/ExtensionSheetPageTest.java @@ -50,6 +50,7 @@ import org.xwiki.rendering.internal.macro.toc.TocMacro; import org.xwiki.rendering.macro.script.MacroPermissionPolicy; import org.xwiki.rendering.transformation.MacroTransformationContext; +import org.xwiki.repository.script.RepositoryScriptService; import org.xwiki.script.service.ScriptService; import org.xwiki.test.annotation.ComponentList; import org.xwiki.test.junit5.mockito.MockComponent; @@ -58,6 +59,7 @@ import org.xwiki.test.page.TestNoScriptMacro; import org.xwiki.test.page.XWikiSyntax21ComponentList; +import com.xpn.xwiki.api.Object; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; @@ -94,7 +96,7 @@ LoggingScriptService.class, PermissionCheckerListener.class, TestNoScriptMacro.class, - TocMacro.class, + TocMacro.class }) class ExtensionSheetPageTest extends PageTest { @@ -189,6 +191,13 @@ void setUp() throws Exception // Mock restricted contexts. when(this.groovyMacroPermissionPolicy.hasPermission(any(), any())).thenAnswer(i -> !((MacroTransformationContext) i.getArgument(1)).getTransformationContext().isRestricted()); + + // Mock repository script service + RepositoryScriptService repositoryScriptService = + this.componentManager.registerMockComponent(ScriptService.class, "repository", + RepositoryScriptService.class, false); + when(repositoryScriptService.getVersionObject(any(), any())).thenReturn(new Object(extensionVersionObject, + null)); } @Test From 0760a2674943de35a4466a2433f23e721e9ac2d7 Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Fri, 3 Oct 2025 15:33:37 +0200 Subject: [PATCH 21/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * codestyle and javadoc --- .../repository/internal/ExtensionStore.java | 54 +++++++++++++------ .../internal/RepositoryManager.java | 36 +++++++++++-- .../internal/XWikiRepositoryModel.java | 4 +- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 5491a669672e..587989a3ea32 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -490,7 +490,9 @@ public BaseObject getExtensionVersionObject(XWikiDocument document, Version vers } /** - * @since 42 + * @param extensionDocument the document containing the extension metadata + * @return true if the passed extension document indicate that version should be proxied + * @since 17.9.0RC1 */ public boolean isVersionProxyingEnabled(XWikiDocument extensionDocument) { @@ -504,6 +506,13 @@ public boolean isVersionProxyingEnabled(XWikiDocument extensionDocument) .equals(getValue(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_PROXYLEVEL, (String) null)); } + /** + * @param extensionId the identifier of the extension + * @return the main document holder the extension metadata, or null if none count be found + * @throws QueryException when failing to search for the extension document + * @throws XWikiException when failing to get the extension document + * @since 17.9.0RC1 + */ public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws QueryException, XWikiException { XWikiContext xcontext = this.xcontextProvider.get(); @@ -534,15 +543,21 @@ public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws : null; } + /** + * @param extensionId the identifier of the extension + * @return the latest version of the extension + * @throws QueryException when failing to search for the extension latest version + * @since 17.9.0RC1 + */ public String getLastVersion(String extensionId) throws QueryException { Query query = this.queryManager.createQuery("select version.version, version.index from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME - + ") as version where version.id = :extensionId and version.index is not null " + + ") as version where version.id = :versionId and version.index is not null " + "order by version.index desc", Query.XWQL); - query.bindValue("extensionId", extensionId); + query.bindValue("versionId", extensionId); query.setLimit(1); List results = query.execute(); @@ -554,13 +569,22 @@ public String getLastVersion(String extensionId) throws QueryException return (String) results.get(0)[0]; } + /** + * @param extensionDocument the document holder the extension metadata + * @return the object holding the main extension metadata + * @since 17.9.0RC1 + */ public BaseObject getExtensionObject(XWikiDocument extensionDocument) { return extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); } /** - * @since 42 + * @param extensionId the identifier of the extension + * @return true if extension version should be stored in dedicated pages + * @throws QueryException when failing to search for the extension page + * @throws XWikiException when failing to get the extension page + * @since 17.9.0RC1 */ public boolean isVersionPageEnabled(String extensionId) throws QueryException, XWikiException { @@ -570,7 +594,9 @@ public boolean isVersionPageEnabled(String extensionId) throws QueryException, X } /** - * @since 42 + * @param extensionDocument the main page holding extension metadata + * @return true if extension version should be stored in dedicated pages + * @since 17.9.0RC1 */ public boolean isVersionPageEnabled(XWikiDocument extensionDocument) { @@ -580,7 +606,9 @@ public boolean isVersionPageEnabled(XWikiDocument extensionDocument) } /** - * @since 42 + * @param extensionOject the main object holding extension metadata + * @return true if extension version should be stored in dedicated pages + * @since 17.9.0RC1 */ public boolean isVersionPageEnabled(BaseObject extensionOject) { @@ -588,7 +616,9 @@ public boolean isVersionPageEnabled(BaseObject extensionOject) } /** - * @since 42 + * @param extensionOject true if extension version should be stored in dedicated pages + * @return true if the object has been modified, false otherwise + * @since 17.9.0RC1 */ public boolean setVersionPageEnabled(BaseObject extensionOject) { @@ -600,14 +630,4 @@ public boolean setVersionPageEnabled(BaseObject extensionOject) return false; } - - /** - * @since 42 - */ - public XWikiDocument getDocument(String fullName) throws XWikiException - { - XWikiContext xcontext = this.xcontextProvider.get(); - - return xcontext.getWiki().getDocument(this.currentStringResolver.resolve(fullName), xcontext); - } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index bcbd7f7ad954..d03a402dab30 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -94,8 +94,14 @@ import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.StringProperty; +/** + * Expose various tools to manipulate extensions in the wiki. + * + * @version $Id$ + */ @Component(roles = RepositoryManager.class) @Singleton +@SuppressWarnings("checkstyle:ClassFanOutComplexity") public class RepositoryManager { private static final Pattern PATTERN_NEWLINE = Pattern.compile("[\n\r]"); @@ -150,7 +156,7 @@ public class RepositoryManager private ExtensionFactory extensionFactory; @Inject - protected ExtensionStore extensionStore; + private ExtensionStore extensionStore; @Inject private Logger logger; @@ -422,12 +428,23 @@ public void validateExtensions() throws QueryException, XWikiException + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension", Query.XWQL); for (String documentName : query.execute()) { - validateExtension(this.extensionStore.getDocument(documentName), true); + validateExtension(getDocument(documentName), true); } } + private XWikiDocument getDocument(String fullName) throws XWikiException + { + XWikiContext xcontext = this.xcontextProvider.get(); + + return xcontext.getWiki().getDocument(this.currentStringResolver.resolve(fullName), xcontext); + } + /** - * @since 42 + * @param document the document holding the extension metadata + * @param extensionVersion the version for which to return the download reference + * @return the download reference + * @throws ResolveException when failing to resolve the download reference + * @throws XWikiException when failing to resolve the download reference */ public ResourceReference getDownloadReference(XWikiDocument document, String extensionVersion) throws ResolveException, XWikiException @@ -758,7 +775,11 @@ private boolean updateVersion(String id, Version version, Extension extension, E } /** - * @since 42 + * @param extensionDocument the document holder the main extension metadata + * @param version the version for which to return the object + * @param xcontext the XWiki Context + * @return the object holding the extension version metadata, or null if none could be found + * @throws XWikiException when failing to get the extension version object */ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, XWikiContext xcontext) throws XWikiException @@ -767,7 +788,12 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str } /** - * @since 42 + * @param extensionDocument the document holder the main extension metadata + * @param version the version for which to return the object + * @param allowProxying true if the method to follow the proxy when the version cannot be found locally + * @param xcontext the XWiki Context + * @return the object holding the extension version metadata, or null if none could be found + * @throws XWikiException when failing to get the extension version object */ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying, XWikiContext xcontext) throws XWikiException diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java index 346f9e83b797..c681940412cd 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/XWikiRepositoryModel.java @@ -52,7 +52,7 @@ public class XWikiRepositoryModel public static final String EXTENSION_SPACENAME = "ExtensionCode"; /** - * @since 42 + * @since 17.9.0RC1 */ public static final String EXTENSIONVERSIONS_SPACENAME = "Versions"; @@ -193,7 +193,7 @@ public class XWikiRepositoryModel public static final String PROP_EXTENSION_PROPERTIES = "properties"; /** - * @since 42 + * @since 17.9.0RC1 */ public static final String PROP_EXTENSION_VERSIONPAGE = "versionPage"; From 06796144c94df817d11ae0620a3c7b0ab22f33ec Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Fri, 3 Oct 2025 18:08:37 +0200 Subject: [PATCH 22/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * add javadoc --- .../org/xwiki/repository/internal/RepositoryManager.java | 2 ++ .../xwiki/repository/script/RepositoryScriptService.java | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index d03a402dab30..3604b8d97ec8 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -780,6 +780,7 @@ private boolean updateVersion(String id, Version version, Extension extension, E * @param xcontext the XWiki Context * @return the object holding the extension version metadata, or null if none could be found * @throws XWikiException when failing to get the extension version object + * @since 17.9.0RC1 */ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, XWikiContext xcontext) throws XWikiException @@ -794,6 +795,7 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str * @param xcontext the XWiki Context * @return the object holding the extension version metadata, or null if none could be found * @throws XWikiException when failing to get the extension version object + * @since 17.9.0RC1 */ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, String version, boolean allowProxying, XWikiContext xcontext) throws XWikiException diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java index 203c11bafbe3..e327285f4395 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/script/RepositoryScriptService.java @@ -133,6 +133,14 @@ public ExtensionSupportPlans resolveExtensionSupportPlans(Collection sup return this.extensionStore.resolveExtensionSupportPlans(supportPlanIds); } + /** + * @param extensionId the identifier of the extension + * @param version the version for which to find the object + * @return the object holding the extension version metadata + * @throws QueryException when failing to get the version object + * @throws XWikiException when failing to get the version object + * @since 17.9.0RC1 + */ public Object getVersionObject(String extensionId, String version) throws QueryException, XWikiException { XWikiDocument extensionDoc = this.extensionStore.getExistingExtensionDocumentById(extensionId); From 9514f08b0446f51dfd556ff8def7a951f194d90d Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Tue, 7 Oct 2025 15:18:17 +0200 Subject: [PATCH 23/24] [Misc] Bulletproofing source link resolution --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 1b0abe5c24ef..a49e3621496c 100644 --- a/pom.xml +++ b/pom.xml @@ -128,6 +128,7 @@ 10.17.1 + https://github.com/${xwiki.github.owner}/${xwiki.github.repository}/tree/master/ HEAD From 006128fba401d2dabbe29289c92e9850286f1a5d Mon Sep 17 00:00:00 2001 From: Thomas Mortagne Date: Thu, 9 Oct 2025 17:18:24 +0200 Subject: [PATCH 24/24] XWIKI-14136: Dedicate a different page for each version of the extension in the Repository * fix various bugs impacting the REST API * update the integration tests --- .../repository/internal/ExtensionStore.java | 69 ++++++++- .../internal/RepositoryManager.java | 132 +++++++++++++----- .../AbstractExtensionRESTResource.java | 50 +++++-- .../ExtensionVersionsRESTResource.java | 36 ++++- .../test/ui/repository/RepositoryIT.java | 53 +++---- .../java/org/xwiki/test/ui/TestUtils.java | 2 + .../java/org/xwiki/test/ui/po/InlinePage.java | 2 + 7 files changed, 254 insertions(+), 90 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java index 587989a3ea32..353ade71e4e9 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/ExtensionStore.java @@ -184,6 +184,18 @@ public T getValue(BaseObject xobject, String propertyName) return getValue(xobject, propertyName, (T) null); } + /** + * @param the expected type of the value to return + * @param xobjects the xobject to try + * @param propertyName the property of the xobject + * @return the value + * @since 17.9.0RC1 + */ + public T getValue(List xobjects, String propertyName) + { + return getValue(xobjects, propertyName, (T) null); + } + /** * @param the expected type of the value to return * @param xobject the xobject @@ -198,7 +210,11 @@ public T getValue(BaseObject xobject, String propertyName, T def) T value = def; if (property != null) { value = (T) property.getValue(); - if (value == null) { + if (value instanceof String stringValue) { + value = (T) StringUtils.defaultIfEmpty(stringValue, (String) def); + } else if (value instanceof Collection collectionValue) { + value = collectionValue.isEmpty() ? def : (T) collectionValue; + } else if (value == null) { value = def; } } @@ -206,6 +222,27 @@ public T getValue(BaseObject xobject, String propertyName, T def) return value; } + /** + * @param the expected type of the value to return + * @param xobjects the xobjects to try + * @param propertyName the property of the xobject + * @param def the value to return if the property is not set + * @return the value + * @since 17.9.0RC1 + */ + public T getValue(List xobjects, String propertyName, T def) + { + for (BaseObject xobject : xobjects) { + T result = getValue(xobject, propertyName, null); + + if (result != null) { + return result; + } + } + + return def; + } + private URL getURLValue(BaseProperty property, boolean fallbackOnDocumentURL, XWikiContext xcontext) { URL url = null; @@ -630,4 +667,34 @@ public boolean setVersionPageEnabled(BaseObject extensionOject) return false; } + + /** + * @param document the document holding the extension metadata + * @return the identifier of the extension + * @since 17.9.0RC1 + */ + public String getExtensionId(XWikiDocument document) + { + return document.getStringValue(XWikiRepositoryModel.PROP_EXTENSION_ID); + } + + /** + * @param document the document holding the extension metadata + * @return the name of the extension + * @since 17.9.0RC1 + */ + public String getExtensionName(XWikiDocument document) + { + return document.getStringValue(XWikiRepositoryModel.PROP_EXTENSION_NAME); + } + + /** + * @param document the document holding the extension metadata + * @return the type of the extension + * @since 17.9.0RC1 + */ + public String getExtensionType(XWikiDocument document) + { + return document.getStringValue(XWikiRepositoryModel.PROP_EXTENSION_TYPE); + } } diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java index 3604b8d97ec8..432fa947245f 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/RepositoryManager.java @@ -68,6 +68,7 @@ import org.xwiki.extension.version.Version.Type; import org.xwiki.extension.version.internal.DefaultVersion; import org.xwiki.extension.version.internal.DefaultVersionConstraint; +import org.xwiki.model.EntityType; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.AttachmentReferenceResolver; import org.xwiki.model.reference.DocumentReference; @@ -87,6 +88,7 @@ import org.xwiki.rendering.syntax.Syntax; import org.xwiki.repository.internal.reference.ExtensionResourceReference; +import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; @@ -237,7 +239,7 @@ public void validateExtension(XWikiDocument document, boolean save) throws XWiki */ private String findLastVersion(XWikiDocument document) throws QueryException { - String extensionId = getExtensionId(document); + String extensionId = this.extensionStore.getExtensionId(document); if (this.extensionStore.isVersionPageEnabled(document)) { return this.extensionStore.getLastVersion(extensionId); @@ -272,11 +274,6 @@ private String findLastVersion(XWikiDocument document) throws QueryException } } - private String getExtensionId(XWikiDocument document) - { - return document.getStringValue(XWikiRepositoryModel.PROP_EXTENSION_ID); - } - private DocumentReference getClassReference(XWikiDocument document, EntityReference localReference) { return this.referenceResolver.resolve(localReference, document.getDocumentReference().getWikiReference()); @@ -639,8 +636,9 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository Set validVersions = new HashSet<>(); - // Check the versions storage mode + // Check the versions storage and proxy mode boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionObject); + boolean versionProxyEnabled = this.extensionStore.isVersionProxyingEnabled(extensionDocument); // Remove unexisting versions List versionObjects = @@ -656,7 +654,7 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository // * the version is blank // * versions should be proxied if (versionPageEnabled || StringUtils.isBlank(version) - || (this.extensionStore.isVersionProxyingEnabled(extensionDocument) + || (versionProxyEnabled && !new DefaultVersion(version).equals(extension.getId().getVersion()))) { extensionDocument.removeXObject(versionObject); needSave = true; @@ -709,6 +707,10 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository } } + // Remove version pages corresponding to not existing versions + cleanNotExistingVersionPages(extensionDocument, extension.getId().getVersion(), extensionVersions, + featureVersions, versionProxyEnabled, xcontext); + // Update features versions long index = 0; for (Map.Entry entry : featureVersions.entrySet()) { @@ -740,6 +742,35 @@ public DocumentReference importExtension(String extensionId, ExtensionRepository return extensionDocument.getDocumentReference(); } + private void cleanNotExistingVersionPages(XWikiDocument extensionDocument, Version currentVersion, + TreeMap extensionVersions, TreeMap featureVersions, + boolean versionProxyEnabled, XWikiContext xcontext) throws QueryException, XWikiException + { + EntityReference versionsSpaceReference = new EntityReference(XWikiRepositoryModel.EXTENSIONVERSIONS_SPACENAME, + EntityType.SPACE, extensionDocument.getDocumentReference().getLocalDocumentReference().getParent()); + Query query = this.queryManager.createQuery( + "select doc.fullName, version.version from Document doc, doc.object(" + + XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME + ") version where doc.space like :space", + Query.XWQL); + query.bindValue("space", this.entityReferenceSerializer.serialize(versionsSpaceReference) + ".%"); + List results = query.execute(); + + XWiki xwiki = xcontext.getWiki(); + for (Object[] result : results) { + Version version = new DefaultVersion((String) result[1]); + + // Remove the document if: + // * the versions does not exist + // * versions should be proxied but it's not the current one + if (versionProxyEnabled || (!currentVersion.equals(version) && !extensionVersions.containsKey(version) + && !featureVersions.containsKey(version))) { + DocumentReference documentReference = this.currentStringResolver.resolve((String) result[0]); + XWikiDocument document = xwiki.getDocument(documentReference, xcontext); + xwiki.deleteDocument(document, xcontext); + } + } + } + private void saveDocument(XWikiDocument document, String comment, XWikiContext xcontext) throws XWikiException { document.setAuthorReference(xcontext.getUserReference()); @@ -818,14 +849,14 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); if (extensionVersionObject == null && allowProxying - && this.extensionStore.isVersionProxyingEnabled(extensionVersionDocument)) { + && this.extensionStore.isVersionProxyingEnabled(extensionDocument)) { // No ExtensionVersionClass object for the version, but proxy is enabled, so try to find remotely Extension extension = null; try { - extension = resolveExtensionVersion(extensionVersionDocument, version); + extension = resolveExtensionVersion(extensionDocument, version); } catch (ExtensionNotFoundException e) { this.logger.debug("No extension could be found remotely with version [{}] for extension page [{}]", - version, extensionVersionDocument.getDocumentReference()); + version, extensionDocument.getDocumentReference()); } catch (ResolveException e) { throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -838,8 +869,8 @@ public BaseObject getExtensionVersionObject(XWikiDocument extensionDocument, Str // Create a "detached" xobject for that extension version // FIXME: find a more elegant solution try { - XWikiDocument extensionDocumentClone = extensionVersionDocument.clone(); - updateExtensionVersion(extension, extensionDocumentClone, 0, false); + XWikiDocument extensionDocumentClone = extensionDocument.clone(); + updateExtensionVersion(extension, extensionDocumentClone, 0, xcontext); extensionVersionObject = extensionDocumentClone .getXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, "version", version, false); } catch (XWikiException e) { @@ -1197,6 +1228,27 @@ private boolean updateExtensionVersionDependencies(Extension extension, XWikiDoc */ public Extension resolveExtensionVersion(XWikiDocument extensionDocument, String extensionVersion) throws ResolveException + { + BaseObject extensionObject = extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); + if (extensionObject == null) { + return null; + } + String extensionId = + this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID, (String) null); + + if (extensionId == null) { + return null; + } + + ExtensionRepository repository = getExtensionRepository(extensionDocument); + if (isGivenVersionOneOfExtensionVersions(repository, extensionId, extensionVersion)) { + return repository.resolve(new ExtensionId(extensionId, extensionVersion)); + } else { + return tryToResolveExtensionFromExtensionFeatures(repository, extensionObject, extensionVersion); + } + } + + public ExtensionRepository getExtensionRepository(XWikiDocument extensionDocument) { BaseObject extensionObject = extensionDocument.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE); if (extensionObject == null) { @@ -1217,12 +1269,7 @@ public Extension resolveExtensionVersion(XWikiDocument extensionDocument, String return null; } - ExtensionRepository repository = this.extensionRepositoryManager.getRepository(repositoryId); - if (isGivenVersionOneOfExtensionVersions(repository, extensionId, extensionVersion)) { - return repository.resolve(new ExtensionId(extensionId, extensionVersion)); - } else { - return tryToResolveExtensionFromExtensionFeatures(repository, extensionObject, extensionVersion); - } + return this.extensionRepositoryManager.getRepository(repositoryId); } /** @@ -1233,7 +1280,8 @@ private Extension tryToResolveExtensionFromExtensionFeatures(ExtensionRepository { List features = (List) this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_FEATURES); - return features.stream().map(feature -> { + + return features == null ? null : features.stream().map(feature -> { try { String featureId = feature.split("/")[0]; return repository.resolve(new ExtensionId(featureId, extensionVersion)); @@ -1268,26 +1316,11 @@ private void moveLegacyVersion(XWikiDocument extensionDocument, BaseObject versi saveDocument(extensionVersionDocument, "Migrate the extension version", xcontext); } - private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, long index, - boolean save) throws XWikiException + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensionVersionDocument, + long index, XWikiContext xcontext) throws XWikiException { boolean needSave = false; - XWikiContext xcontext = this.xcontextProvider.get(); - - // Resolve the version document - XWikiDocument extensionVersionDocument = this.extensionStore.getExtensionVersionDocument(extensiondocument, - extensionVersion.getId().getVersion(), xcontext); - if (extensionVersionDocument.isNew()) { - // New document version - needSave = true; - } - - // Avoid modifying the cached document - if (save) { - extensionVersionDocument = extensionVersionDocument.clone(); - } - // Update version object BaseObject versionObject = this.extensionStore.getExtensionVersionObject(extensionVersionDocument, extensionVersion.getId().getVersion()); @@ -1327,6 +1360,31 @@ private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument // index needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_INDEX, index); + return needSave; + } + + private boolean updateExtensionVersion(Extension extensionVersion, XWikiDocument extensiondocument, long index, + boolean save) throws XWikiException + { + boolean needSave = false; + + XWikiContext xcontext = this.xcontextProvider.get(); + + // Resolve the version document + XWikiDocument extensionVersionDocument = this.extensionStore.getExtensionVersionDocument(extensiondocument, + extensionVersion.getId().getVersion(), xcontext); + if (extensionVersionDocument.isNew()) { + // New document version + needSave = true; + } + + // Avoid modifying the cached document + if (save) { + extensionVersionDocument = extensionVersionDocument.clone(); + } + + needSave |= updateExtensionVersion(extensionVersion, extensionVersionDocument, index, xcontext); + // Save if dedicated version page if (save) { // Make sure the Versions home page exist diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java index f1da4b724a9a..803ef2499e62 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/AbstractExtensionRESTResource.java @@ -63,6 +63,7 @@ import org.xwiki.extension.repository.xwiki.model.jaxb.Namespaces; import org.xwiki.extension.repository.xwiki.model.jaxb.ObjectFactory; import org.xwiki.extension.repository.xwiki.model.jaxb.Property; +import org.xwiki.extension.version.Version; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.query.Query; @@ -248,11 +249,14 @@ private Query createExtensionsQuery(String select, String from, String where, in queryStr.append('('); queryStr.append(where); queryStr.append(')'); - queryStr.append(" and "); } - queryStr.append("extension." + XWikiRepositoryModel.PROP_EXTENSION_VALIDEXTENSION + " = 1"); if (versions && pageVersion) { queryStr.append(" order by extensionVersion." + XWikiRepositoryModel.PROP_VERSION_INDEX); + } else { + if (where != null) { + queryStr.append(" and "); + } + queryStr.append("extension." + XWikiRepositoryModel.PROP_EXTENSION_VALIDEXTENSION + " = 1"); } Query query = this.queryManager.createQuery(queryStr.toString(), Query.XWQL); @@ -327,10 +331,12 @@ protected E createExtension(XWikiDocument extensio AbstractExtension extension; ExtensionVersion extensionVersion; BaseObject extensionVersionObject; + List extensionVersionObjects; if (version == null) { extension = this.extensionObjectFactory.createExtension(); extensionVersion = null; extensionVersionObject = extensionObject; + extensionVersionObjects = List.of(extensionVersionObject); } else { extensionVersionObject = this.repositoryManager.getExtensionVersionObject(extensionDocument, version, getXWikiContext()); @@ -343,23 +349,25 @@ protected E createExtension(XWikiDocument extensio extension = extensionVersion; extensionVersion.setVersion( this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION)); + + extensionVersionObjects = List.of(extensionVersionObject, extensionObject); } - extension.setId(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_ID)); + extension.setId(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_ID)); extension.setType(StringUtils.stripToNull( - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_TYPE))); extension.setRating(getExtensionRating(extensionDocumentReference)); extension.setSummary( - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY)); extension.setDescription( this.extensionStore.getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION)); extension - .setName(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_NAME)); + .setName(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_NAME)); extension.setCategory( - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_CATEGORY)); + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_CATEGORY)); extension.setWebsite(StringUtils.defaultIfEmpty( - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE), + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE), extensionDocument.getExternalURL("view", getXWikiContext()))); // Recommended @@ -372,18 +380,18 @@ protected E createExtension(XWikiDocument extensio // SCM ExtensionScm scm = new ExtensionScm(); - scm.setUrl(this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMURL)); + scm.setUrl(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_SCMURL)); scm.setConnection(toScmConnection( - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMCONNECTION))); - scm.setDeveloperConnection(toScmConnection(this.extensionStore.getValue(extensionVersionObject, + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_SCMCONNECTION))); + scm.setDeveloperConnection(toScmConnection(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_SCMDEVCONNECTION))); extension.setScm(scm); // Issue Management ExtensionIssueManagement issueManagement = new ExtensionIssueManagement(); - issueManagement.setSystem(this.extensionStore.getValue(extensionVersionObject, + issueManagement.setSystem(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_SYSTEM)); - issueManagement.setUrl(this.extensionStore.getValue(extensionVersionObject, + issueManagement.setUrl(this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_URL)); if (StringUtils.isNotEmpty(issueManagement.getSystem()) || StringUtils.isNotEmpty(issueManagement.getUrl())) { extension.setIssueManagement(issueManagement); @@ -391,7 +399,7 @@ protected E createExtension(XWikiDocument extensio // Authors addExtensionAuthors(extension, - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS)); + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS)); // Features List features = @@ -420,7 +428,7 @@ protected E createExtension(XWikiDocument extensio if (extensionVersion != null) { List repositories = - this.extensionStore.getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES); + this.extensionStore.getValue(extensionVersionObjects, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES); extensionVersion.withRepositories(toExtensionRepositories(repositories)); // Dependencies @@ -739,6 +747,18 @@ protected ExtensionRating getExtensionRating(DocumentReference extensionDocument return extensionRating; } + protected ExtensionVersionSummary createExtensionVersionSummary(String extensionId, String type, String name, + Version version) + { + ExtensionVersionSummary extensionVersion = this.extensionObjectFactory.createExtensionVersionSummary(); + extensionVersion.setVersion(version.getValue()); + extensionVersion.setId(extensionId); + extensionVersion.setType(type); + extensionVersion.setName(name); + + return extensionVersion; + } + protected void getExtensionSummaries(List extensions, Query query) throws QueryException { diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java index c466d46b94e7..d2ecbaf60b5c 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-server-api/src/main/java/org/xwiki/repository/internal/resources/ExtensionVersionsRESTResource.java @@ -33,9 +33,13 @@ import org.apache.commons.lang3.StringUtils; import org.xwiki.component.annotation.Component; +import org.xwiki.extension.ResolveException; +import org.xwiki.extension.repository.ExtensionRepository; +import org.xwiki.extension.repository.result.IterableResult; import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersionSummary; import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersions; import org.xwiki.extension.version.InvalidVersionRangeException; +import org.xwiki.extension.version.Version; import org.xwiki.extension.version.VersionConstraint; import org.xwiki.extension.version.internal.DefaultVersion; import org.xwiki.query.Query; @@ -43,6 +47,7 @@ import org.xwiki.repository.Resources; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.doc.XWikiDocument; /** * @version $Id$ @@ -58,17 +63,36 @@ public ExtensionVersions getExtensionVersions(@PathParam("extensionId") String e @QueryParam(Resources.QPARAM_LIST_START) @DefaultValue("0") int offset, @QueryParam(Resources.QPARAM_LIST_NUMBER) @DefaultValue("-1") int number, @QueryParam(Resources.QPARAM_VERSIONS_RANGES) String ranges) - throws QueryException, InvalidVersionRangeException, XWikiException + throws QueryException, InvalidVersionRangeException, XWikiException, ResolveException { - boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionId); - Query query = - createExtensionsSummariesQuery(null, "extensionVersion.id = :extensionId", 0, -1, true, versionPageEnabled); + XWikiDocument extensionDocument = getExistingExtensionDocumentById(extensionId); - query.bindValue("extensionId", extensionId); + checkRights(extensionDocument); ExtensionVersions extensions = this.extensionObjectFactory.createExtensionVersions(); - getExtensionSummaries(extensions.getExtensionVersionSummaries(), query); + if (this.extensionStore.isVersionProxyingEnabled(extensionDocument)) { + ExtensionRepository repository = this.repositoryManager.getExtensionRepository(extensionDocument); + if (repository != null) { + IterableResult versions = repository.resolveVersions(extensionId, 0, -1); + + String extensionName = this.extensionStore.getExtensionName(extensionDocument); + String extensionType = this.extensionStore.getExtensionType(extensionDocument);; + + for (Version version : versions) { + extensions.getExtensionVersionSummaries() + .add(createExtensionVersionSummary(extensionId, extensionType, extensionName, version)); + } + } + } else { + boolean versionPageEnabled = this.extensionStore.isVersionPageEnabled(extensionDocument); + Query query = createExtensionsSummariesQuery(null, "extensionVersion.id = :extensionId", 0, -1, true, + versionPageEnabled); + + query.bindValue("extensionId", extensionId); + + getExtensionSummaries(extensions.getExtensionVersionSummaries(), query); + } // Filter by ranges if (StringUtils.isNotBlank(ranges)) { diff --git a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-test/xwiki-platform-repository-test-tests/src/test/it/org/xwiki/repository/test/ui/repository/RepositoryIT.java b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-test/xwiki-platform-repository-test-tests/src/test/it/org/xwiki/repository/test/ui/repository/RepositoryIT.java index b14177e265a5..5a4880412d6e 100644 --- a/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-test/xwiki-platform-repository-test-tests/src/test/it/org/xwiki/repository/test/ui/repository/RepositoryIT.java +++ b/xwiki-platform-core/xwiki-platform-repository/xwiki-platform-repository-test/xwiki-platform-repository-test-tests/src/test/it/org/xwiki/repository/test/ui/repository/RepositoryIT.java @@ -39,7 +39,10 @@ import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionsSearchResult; import org.xwiki.extension.repository.xwiki.model.jaxb.Property; import org.xwiki.extension.version.internal.DefaultVersionConstraint; +import org.xwiki.model.EntityType; +import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.LocalDocumentReference; +import org.xwiki.rendering.syntax.Syntax; import org.xwiki.repository.Resources; import org.xwiki.repository.internal.XWikiRepositoryModel; import org.xwiki.repository.test.TestExtension; @@ -56,11 +59,10 @@ import org.xwiki.repository.test.po.edit.ExtensionSupporterInlinePage; import org.xwiki.repository.test.ui.AbstractExtensionAdminAuthenticatedIT; import org.xwiki.rest.model.jaxb.Page; -import org.xwiki.test.ui.po.editor.ObjectEditPage; -import org.xwiki.test.ui.po.editor.ObjectEditPane; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -473,10 +475,10 @@ private void testRestAccessToImportedExtension() throws Exception assertEquals("jar", extension.getType()); assertEquals("1.0", extension.getVersion()); assertEquals("name", extension.getName()); - assertEquals("summary2", extension.getSummary()); + assertEquals("summary", extension.getSummary()); assertEquals("summary2\n some more details", extension.getDescription()); - assertEquals(this.baseAuthor.getName(), extension.getAuthors().get(0).getName()); - assertEquals(this.baseAuthor.getURL().toString(), extension.getAuthors().get(0).getUrl()); + assertEquals("Previous Name", extension.getAuthors().get(0).getName()); + assertNull(extension.getAuthors().get(0).getUrl()); assertEquals(Arrays.asList("maven:oldextension", "maven:oldversionnedextension"), extension.getFeatures()); assertEquals("maven:oldextension", extension.getExtensionFeatures().get(0).getId()); @@ -492,14 +494,14 @@ private void testRestAccessToImportedExtension() throws Exception extension = getUtil().rest().getResource(Resources.EXTENSION_VERSION, null, "maven:extension", "0.9"); - assertEquals("maven:extension", extension.getId()); + assertEquals("maven:oldextension", extension.getId()); assertEquals("jar", extension.getType()); assertEquals("0.9", extension.getVersion()); assertEquals("name", extension.getName()); assertEquals("summary2", extension.getSummary()); assertEquals("summary2\n some more details", extension.getDescription()); - assertEquals(this.baseAuthor.getName(), extension.getAuthors().get(0).getName()); - assertEquals(this.baseAuthor.getURL().toString(), extension.getAuthors().get(0).getUrl()); + assertEquals("Old Name", extension.getAuthors().get(0).getName()); + assertNull(extension.getAuthors().get(0).getUrl()); assertEquals(Arrays.asList(), extension.getFeatures()); assertEquals(Arrays.asList(), extension.getExtensionFeatures()); assertEquals("GNU Lesser General Public License 2.1", extension.getLicenses().get(0).getName()); @@ -517,8 +519,7 @@ private void enableProxying(ExtensionPage extensionPage) throws Exception new LocalDocumentReference(List.of("Extension", importedExtensionName), "WebHome"); // assert that this test is going to make sense at all - assertTrue(getNumberOfExtensionVersionsObjects(importedExtensionName) > 1); - assertTrue(getNumberOfExtensionVersionsDependenciesObjects(importedExtensionName) > 1); + assertTrue(getNumberOfExtensionVersionsPages(extensionPageReference) > 1); // indicate that the history of the extension should be proxied getUtil().updateObject(extensionPageReference, XWikiRepositoryModel.EXTENSIONPROXY_CLASSNAME, 0, "proxyLevel", @@ -527,9 +528,8 @@ private void enableProxying(ExtensionPage extensionPage) throws Exception // refresh extension extensionPage.updateExtension(); - // assert that the object to be proxied are now absent - assertEquals(1, getNumberOfExtensionVersionsObjects(importedExtensionName)); - assertEquals(1, getNumberOfExtensionVersionsDependenciesObjects(importedExtensionName)); + // assert that the version to be proxied are now absent + assertEquals(1, getNumberOfExtensionVersionsPages(extensionPageReference)); // Remember the page version Page restPage = getUtil().rest().get(extensionPageReference); @@ -543,25 +543,16 @@ private void enableProxying(ExtensionPage extensionPage) throws Exception assertEquals(extensionPageVersion, restPage.getVersion()); } - private int getNumberOfExtensionVersionsObjects(String extensionName) - { - ObjectEditPage objectEditPage = goToObjectEditPage(extensionName); - List versionObjects = - objectEditPage.getObjectsOfClass(XWikiRepositoryModel.EXTENSIONVERSION_CLASSNAME); - return versionObjects.size(); - } - - private int getNumberOfExtensionVersionsDependenciesObjects(String extensionName) - { - ObjectEditPage objectEditPage = goToObjectEditPage(extensionName); - List dependenciesObjects = - objectEditPage.getObjectsOfClass(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSNAME); - return dependenciesObjects.size(); - } - - private ObjectEditPage goToObjectEditPage(String extensionName) + private int getNumberOfExtensionVersionsPages(LocalDocumentReference extensionPageReference) throws Exception { - return getRepositoryTestUtils().gotoExtensionObjectsEditPage(extensionName); + EntityReference versionReference = new EntityReference(XWikiRepositoryModel.EXTENSIONVERSIONS_SPACENAME, + EntityType.SPACE, extensionPageReference.getParent()); + String result = getUtil().executeWikiPlain( + "{{velocity}}$services.query.xwql(\"select space.reference from Space space where space.parent='" + + getUtil().serializeLocalReference(versionReference) + "'\").execute().size(){{/velocity}}", + Syntax.XWIKI_2_1); + + return Integer.parseInt(result); } private void validateSupport() throws Exception diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java index 74898686019e..bdf3ad51847f 100644 --- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java +++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/TestUtils.java @@ -104,6 +104,7 @@ import org.xwiki.rest.resources.objects.ObjectsResource; import org.xwiki.rest.resources.pages.PageResource; import org.xwiki.rest.resources.pages.PageTranslationResource; +import org.xwiki.rest.resources.pages.PagesResource; import org.xwiki.test.integration.XWikiExecutor; import org.xwiki.test.ui.po.BasePage; import org.xwiki.test.ui.po.ViewPage; @@ -2539,6 +2540,7 @@ public ResourceAPI(Class api, Class localeAPI) throw new RuntimeException(e); } + RESOURCES_MAP.put(EntityType.SPACE, new ResourceAPI(PagesResource.class, null)); RESOURCES_MAP.put(EntityType.DOCUMENT, new ResourceAPI(PageResource.class, PageTranslationResource.class)); RESOURCES_MAP.put(EntityType.OBJECT, new ResourceAPI(ObjectResource.class, null)); RESOURCES_MAP.put(EntityType.OBJECT_PROPERTY, new ResourceAPI(ObjectPropertyResource.class, null)); diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/InlinePage.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/InlinePage.java index cd3e39a8b5be..b20cc9f3fea7 100644 --- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/InlinePage.java +++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/InlinePage.java @@ -102,6 +102,8 @@ public T clickSaveAndView() public void clickSaveAndView(boolean wait) { if (wait) { + // Increase the timeout as a page save can be slow + getDriver().setTimeout(30); getDriver().addPageNotYetReloadedMarker(); }