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}