001/* 002 * Copyright 2025 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.trash; 017 018import java.util.Optional; 019 020import javax.jcr.RepositoryException; 021 022import org.ametys.cms.trash.element.TrashElementDAO; 023import org.ametys.plugins.repository.AmetysObject; 024import org.ametys.plugins.repository.AmetysRepositoryException; 025import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 026import org.ametys.plugins.repository.UnknownAmetysObjectException; 027import org.ametys.plugins.repository.jcr.JCRAmetysObject; 028import org.ametys.plugins.repository.trash.TrashableAmetysObject; 029import org.ametys.web.repository.SiteAwareAmetysObject; 030import org.ametys.web.repository.site.Site; 031 032/** 033 * Add supports for a trash by site 034 */ 035public class WebTrashElementDAO extends TrashElementDAO 036{ 037 private static final String __SITES_TRASH_ROOT_NODE_NAME = "ametys-internal:sites-trash"; 038 039 /** 040 * Get the trash root for the object. 041 * The root can be the root for all element shared between sites 042 * or the root for the site of the element 043 */ 044 @Override 045 protected ModifiableTraversableAmetysObject getOrCreateRoot(TrashableAmetysObject ametysObject) 046 { 047 // If the object is site aware use it, otherwise, try to guess the site based on the path 048 String siteName = ametysObject instanceof SiteAwareAmetysObject siteAO 049 ? siteAO.getSiteName() 050 : _getParentSiteName(ametysObject); 051 052 return siteName != null 053 ? _getOrCreateSiteRoot(siteName) // get the root for the site 054 : super.getOrCreateRoot(ametysObject); // get the root for the shared object 055 } 056 057 private ModifiableTraversableAmetysObject _getOrCreateSiteRoot(String siteName) 058 { 059 return executeInTrashSession( 060 session -> 061 { 062 ModifiableTraversableAmetysObject root = _resolver.resolveByPath("/", session); 063 ModifiableTraversableAmetysObject sitesTrash = getOrCreateCollection(root, __SITES_TRASH_ROOT_NODE_NAME); 064 return getOrCreateCollection(sitesTrash, siteName); 065 } 066 ); 067 } 068 069 // Search the sitename on parents of the object 070 private String _getParentSiteName(AmetysObject ametysObject) 071 { 072 if (ametysObject instanceof Site site) 073 { 074 return site.getName(); 075 } 076 else if (ametysObject != null) 077 { 078 return _getParentSiteName(ametysObject.getParent()); 079 } 080 else 081 { 082 return null; 083 } 084 } 085 086 @Override 087 public void empty(TrashElementFilter filter) throws AmetysRepositoryException 088 { 089 if (!(filter instanceof WebTrashElementFilter webFilter)) 090 { 091 super.empty(filter); 092 return; 093 } 094 095 if (webFilter.allowCommon()) 096 { 097 Optional<String> rootPath = executeInTrashSession(session -> { 098 try 099 { 100 JCRAmetysObject trashRoot = _resolver.resolveByPath(__TRASH_ROOT_NODE_NAME, session); 101 return Optional.of(getEncodedJCRPath(trashRoot)); 102 } 103 catch (UnknownAmetysObjectException e) 104 { 105 return Optional.empty(); 106 } 107 catch (RepositoryException e) 108 { 109 throw new AmetysRepositoryException(e); 110 } 111 }); 112 113 // empty means that the root is not initialized 114 // do not forward it to the empty method has it means no filter to it 115 if (rootPath.isPresent()) 116 { 117 empty(rootPath, webFilter.getMinimumAge()); 118 } 119 } 120 121 if (webFilter.getSitename().isPresent()) 122 { 123 Optional<String> rootPath = executeInTrashSession(session -> { 124 try 125 { 126 JCRAmetysObject trashRoot = _resolver.resolveByPath(__SITES_TRASH_ROOT_NODE_NAME + "/" + webFilter.getSitename().get(), session); 127 return Optional.of(getEncodedJCRPath(trashRoot)); 128 } 129 catch (UnknownAmetysObjectException e) 130 { 131 return Optional.empty(); 132 } 133 catch (RepositoryException e) 134 { 135 throw new AmetysRepositoryException(e); 136 } 137 }); 138 139 // empty means that the root is not initialized 140 // do not forward it to the empty method has it means no filter to it 141 if (rootPath.isPresent()) 142 { 143 empty(rootPath, webFilter.getMinimumAge()); 144 } 145 } 146 } 147 148 /** 149 * Information related to filtering trash element targeted by a mass operation 150 * This implementation handle the segregation of trash by site 151 */ 152 public static class WebTrashElementFilter extends TrashElementFilter 153 { 154 private Optional<String> _sitename; 155 private boolean _common; 156 157 /** 158 * Information related to filtering trash element targeted by a mass operation 159 * @param sitename set this to target element of a given site. if empty, no element of any site will be targeted 160 * @param common if true, element of the common trash will be targeted 161 * @param age Minimum age (in days) of the element. 0 or less means no filtering. 162 */ 163 public WebTrashElementFilter(Optional<String> sitename, boolean common, long age) 164 { 165 super(age); 166 _sitename = sitename; 167 _common = common; 168 } 169 170 /** 171 * Get the name of the site to target 172 * @return the site name or empty if no site's trash element should be targeted 173 */ 174 public Optional<String> getSitename() 175 { 176 return _sitename; 177 } 178 179 /** 180 * Indicate if element from the common trash should be targeted 181 * @return true if the common trash should be targeted 182 */ 183 public boolean allowCommon() 184 { 185 return _common; 186 } 187 } 188}