207207import com .cloud .api .query .dao .SnapshotJoinDao ;
208208import com .cloud .api .query .dao .StoragePoolJoinDao ;
209209import com .cloud .api .query .dao .TemplateJoinDao ;
210+ import com .cloud .api .query .dao .TemplateListFilter ;
210211import com .cloud .api .query .dao .UserAccountJoinDao ;
211212import com .cloud .api .query .dao .UserVmJoinDao ;
212213import com .cloud .api .query .dao .VolumeJoinDao ;
328329import com .cloud .storage .dao .VolumeDao ;
329330import com .cloud .tags .ResourceTagVO ;
330331import com .cloud .tags .dao .ResourceTagDao ;
332+ import com .cloud .template .VirtualMachineTemplate ;
331333import com .cloud .template .VirtualMachineTemplate .State ;
332334import com .cloud .template .VirtualMachineTemplate .TemplateFilter ;
333335import com .cloud .user .Account ;
@@ -4989,8 +4991,9 @@ private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long temp
49894991 sc .setJoinParameters ("storagePool" , "pool_id" , storagePoolId );
49904992 }
49914993
4994+ List <Long > guestOsIds = null ;
49924995 if (osCategoryId != null ) {
4993- List < Long > guestOsIds = guestOSDao .listIdsByCategoryId (osCategoryId );
4996+ guestOsIds = guestOSDao .listIdsByCategoryId (osCategoryId );
49944997 if (CollectionUtils .isNotEmpty (guestOsIds )) {
49954998 sc .setParameters ("guestOsIdIN" , guestOsIds .toArray ());
49964999 } else {
@@ -5146,7 +5149,12 @@ else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN)
51465149 applyPublicTemplateSharingRestrictions (sc , caller );
51475150
51485151 return templateChecks (isIso , hypers , tags , name , keyword , hyperType , onlyReady , bootable , zoneId , showDomr , caller ,
5149- showRemovedTmpl , parentTemplateId , showUnique , templateType , isVnf , forCks , searchFilter , sc );
5152+ showRemovedTmpl , parentTemplateId , showUnique , templateType , isVnf , forCks , searchFilter , sc ,
5153+ buildTemplateListFilter (templateId , ids , name , keyword , templateFilter , isIso , bootable ,
5154+ pageSize , startIndex , zoneId , hyperType , hypers , showDomr , onlyReady ,
5155+ permittedAccounts , caller , listProjectResourcesCriteria , tags , showRemovedTmpl ,
5156+ parentTemplateId , showUnique , templateType , isVnf , forCks , arch , guestOsIds , extensionId ,
5157+ imageStoreId , storagePoolId , domainId , isRecursive ));
51505158 }
51515159
51525160 /**
@@ -5201,7 +5209,8 @@ protected boolean checkIfDomainSharesTemplates(Long domainId) {
52015209 private Pair <List <TemplateJoinVO >, Integer > templateChecks (boolean isIso , List <HypervisorType > hypers , Map <String , String > tags , String name , String keyword ,
52025210 HypervisorType hyperType , boolean onlyReady , Boolean bootable , Long zoneId , boolean showDomr , Account caller ,
52035211 boolean showRemovedTmpl , Long parentTemplateId , Boolean showUnique , String templateType , Boolean isVnf , Boolean forCks ,
5204- Filter searchFilter , SearchCriteria <TemplateJoinVO > sc ) {
5212+ Filter searchFilter , SearchCriteria <TemplateJoinVO > sc ,
5213+ TemplateListFilter listFilter ) {
52055214 if (!isIso ) {
52065215 // add hypervisor criteria for template case
52075216 if (hypers != null && !hypers .isEmpty ()) {
@@ -5308,6 +5317,14 @@ private Pair<List<TemplateJoinVO>, Integer> templateChecks(boolean isIso, List<H
53085317
53095318 // search unique templates and find details by Ids
53105319 Pair <List <TemplateJoinVO >, Integer > uniqueTmplPair ;
5320+
5321+ // Only attempted when the bypass DAO supports the filter set; falls
5322+ // back to the SearchBuilder/template_view path below otherwise.
5323+ if (!showRemovedTmpl && BypassTemplateView .value () && listFilter != null && listFilter .canBypass ()) {
5324+ uniqueTmplPair = _templateJoinDao .findDistinctTempZonePairs (listFilter );
5325+ return findTemplatesByIdOrTempZonePair (uniqueTmplPair , showRemovedTmpl , showUnique != null && showUnique , caller );
5326+ }
5327+
53115328 if (showRemovedTmpl ) {
53125329 uniqueTmplPair = _templateJoinDao .searchIncludingRemovedAndCount (sc , searchFilter );
53135330 } else {
@@ -5329,6 +5346,140 @@ private Pair<List<TemplateJoinVO>, Integer> templateChecks(boolean isIso, List<H
53295346 // about what the code was doing.
53305347 }
53315348
5349+ /**
5350+ * Build the immutable filter the bypass-the-view DAO consumes. Mirrors the
5351+ * SearchCriteria construction in {@link #searchForTemplatesInternal}; the
5352+ * SearchBuilder path is unchanged. The "hard" markers (sharedAccountIds,
5353+ * domainIdsForFeaturedCommunity, tags, requiresViewFallback) drive
5354+ * {@link TemplateListFilter#canBypass()} — when any are populated,
5355+ * {@code templateChecks} routes to the legacy view-based path instead.
5356+ */
5357+ private TemplateListFilter buildTemplateListFilter (Long templateId , List <Long > ids , String name , String keyword ,
5358+ TemplateFilter templateFilter , boolean isIso , Boolean bootable ,
5359+ Long pageSize , Long startIndex , Long zoneId , HypervisorType hyperType ,
5360+ List <HypervisorType > hypers , boolean showDomr , boolean onlyReady ,
5361+ List <Account > permittedAccounts , Account caller ,
5362+ ListProjectResourcesCriteria listProjectResourcesCriteria ,
5363+ Map <String , String > tags , boolean showRemovedTmpl , Long parentTemplateId ,
5364+ Boolean showUnique , String templateType , Boolean isVnf , Boolean forCks ,
5365+ CPU .CPUArch arch , List <Long > guestOsIds , Long extensionId , Long imageStoreId ,
5366+ Long storagePoolId , Long domainId , boolean isRecursive ) {
5367+ TemplateListFilter .Builder b = TemplateListFilter .builder ()
5368+ .templateId (templateId )
5369+ .ids (ids == null ? null : new ArrayList <>(ids ))
5370+ .name (name )
5371+ .keyword (keyword )
5372+ .hypervisorType (hyperType != null && !HypervisorType .None .equals (hyperType ) ? hyperType : null )
5373+ .availableHypervisors (hypers )
5374+ .format (ImageFormat .ISO ) // bypass DAO uses isIso flag to flip between EQ and NEQ
5375+ .isIso (isIso )
5376+ .bootable (bootable )
5377+ .parentTemplateId (parentTemplateId )
5378+ .zoneId (zoneId )
5379+ .onlyReady (onlyReady )
5380+ .excludeSystemTemplates (!showDomr )
5381+ .templateType (templateType )
5382+ .isVnf (isVnf )
5383+ .forCks (forCks )
5384+ .arch (arch )
5385+ .guestOsIds (guestOsIds )
5386+ .extensionId (extensionId )
5387+ .imageStoreId (imageStoreId )
5388+ .showUnique (showUnique != null && showUnique )
5389+ .startIndex (startIndex )
5390+ .pageSize (pageSize )
5391+ .sortAscending (SortKeyAscending .value ())
5392+ .showRemoved (showRemovedTmpl )
5393+ .tags (tags );
5394+
5395+ if (!showRemovedTmpl ) {
5396+ b .templateStates (Arrays .asList (VirtualMachineTemplate .State .Active ,
5397+ VirtualMachineTemplate .State .UploadAbandoned ,
5398+ VirtualMachineTemplate .State .UploadError ,
5399+ VirtualMachineTemplate .State .NotUploaded ,
5400+ VirtualMachineTemplate .State .UploadInProgress ));
5401+ }
5402+
5403+ // accountType filter (Project vs non-Project), applied only when no specific templateId.
5404+ if (templateId == null && listProjectResourcesCriteria == ListProjectResourcesCriteria .SkipProjectResources ) {
5405+ b .accountTypeNeq (Account .Type .PROJECT );
5406+ } else if (templateId == null && listProjectResourcesCriteria == ListProjectResourcesCriteria .ListProjectResourcesOnly ) {
5407+ b .accountTypeEq (Account .Type .PROJECT );
5408+ }
5409+
5410+ // ACL handling — mirrors the templateFilter switch in searchForTemplatesInternal.
5411+ // For "hard" templateFilter values, set requiresViewFallback so canBypass() returns
5412+ // false and the dispatcher falls back to the legacy view-based path.
5413+ if (templateId != null ) {
5414+ return b .build ();
5415+ }
5416+
5417+ // template_spool_ref join is not modeled in bypass SQL.
5418+ if (storagePoolId != null ) {
5419+ b .requiresViewFallback (true );
5420+ }
5421+
5422+ List <Long > permittedAccountIds = new ArrayList <>();
5423+ for (Account account : permittedAccounts ) {
5424+ permittedAccountIds .add (account .getId ());
5425+ }
5426+
5427+ if (templateFilter == TemplateFilter .featured || templateFilter == TemplateFilter .community ) {
5428+ // Walks the domain hierarchy and ORs domainId IS NULL — not yet modeled in bypass SQL.
5429+ b .publicTemplate (Boolean .TRUE );
5430+ b .featured (templateFilter == TemplateFilter .featured ? Boolean .TRUE : Boolean .FALSE );
5431+ b .requiresViewFallback (true );
5432+ } else if (templateFilter == TemplateFilter .self || templateFilter == TemplateFilter .selfexecutable ) {
5433+ if (accountMgr .isAdmin (caller .getAccountId ())) {
5434+ applySelfDomainScope (b , permittedAccounts , caller , domainId , isRecursive );
5435+ }
5436+ if (!permittedAccountIds .isEmpty ()) {
5437+ b .accountIds (permittedAccountIds );
5438+ b .accountIdRequiredForFilter (true );
5439+ }
5440+ } else if (templateFilter == TemplateFilter .sharedexecutable || templateFilter == TemplateFilter .shared ) {
5441+ // launch_permission join — not modeled in bypass SQL.
5442+ b .requiresViewFallback (true );
5443+ } else if (templateFilter == TemplateFilter .executable ) {
5444+ b .publicOrAccountIdComposite (true );
5445+ if (!permittedAccountIds .isEmpty ()) {
5446+ b .accountIds (permittedAccountIds );
5447+ }
5448+ } else if (templateFilter == TemplateFilter .all && caller .getType () != Account .Type .ADMIN ) {
5449+ // Non-admin "all" composite splits on listProjectResourcesCriteria:
5450+ // SkipProjectResources → (public OR domainPath LIKE) — bypass-eligible
5451+ // else → (public OR account_id IN OR sharedAccountId IN) — needs launch_permission, falls back
5452+ if (listProjectResourcesCriteria == ListProjectResourcesCriteria .SkipProjectResources ) {
5453+ DomainVO callerDomain = _domainDao .findById (caller .getDomainId ());
5454+ if (callerDomain != null ) {
5455+ b .publicOrDomainPathComposite (true );
5456+ b .domainPathLike (callerDomain .getPath () + "%" );
5457+ } else {
5458+ b .requiresViewFallback (true );
5459+ }
5460+ } else {
5461+ b .requiresViewFallback (true );
5462+ }
5463+ }
5464+
5465+ return b .build ();
5466+ }
5467+
5468+ // Mirrors the admin domain-path scoping of the view path for self/selfexecutable.
5469+ private void applySelfDomainScope (TemplateListFilter .Builder b , List <Account > permittedAccounts , Account caller ,
5470+ Long domainId , boolean isRecursive ) {
5471+ Long domainIdForScope = !permittedAccounts .isEmpty ()
5472+ ? permittedAccounts .get (0 ).getDomainId ()
5473+ : Objects .requireNonNullElse (domainId , caller .getDomainId ());
5474+ DomainVO scopeDomain = _domainDao .findById (domainIdForScope );
5475+ // A recursive scope from the root domain matches every row, so skip the domain join.
5476+ if (scopeDomain == null || (isRecursive && scopeDomain .getId () == Domain .ROOT_DOMAIN )) {
5477+ return ;
5478+ }
5479+ b .domainPathLike (isRecursive ? scopeDomain .getPath () + "%" : scopeDomain .getPath ());
5480+ b .domainPathExact (!isRecursive );
5481+ }
5482+
53325483 // findTemplatesByIdOrTempZonePair returns the templates with the given ids if showUnique is true, or else by the TempZonePair
53335484 private Pair <List <TemplateJoinVO >, Integer > findTemplatesByIdOrTempZonePair (Pair <List <TemplateJoinVO >, Integer > templateDataPair ,
53345485 boolean showRemoved , boolean showUnique , Account caller ) {
@@ -6342,7 +6493,8 @@ public String getConfigComponentName() {
63426493 @ Override
63436494 public ConfigKey <?>[] getConfigKeys () {
63446495 return new ConfigKey <?>[] {AllowUserViewDestroyedVM , UserVMDeniedDetails , UserVMReadOnlyDetails , SortKeyAscending ,
6345- AllowUserViewAllDomainAccounts , AllowUserViewAllDataCenters , SharePublicTemplatesWithOtherDomains , ReturnVmStatsOnVmList };
6496+ AllowUserViewAllDomainAccounts , AllowUserViewAllDataCenters , SharePublicTemplatesWithOtherDomains ,
6497+ ReturnVmStatsOnVmList , BypassTemplateView };
63466498 }
63476499
63486500 @ Override
0 commit comments