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.cms.repository;
017
018import java.util.HashMap;
019import java.util.Map;
020
021import javax.jcr.NodeIterator;
022import javax.jcr.Repository;
023import javax.jcr.RepositoryException;
024import javax.jcr.Session;
025import javax.jcr.query.Query;
026import javax.jcr.query.QueryManager;
027import javax.jcr.query.QueryResult;
028import javax.jcr.version.VersionHistory;
029
030import org.apache.avalon.framework.component.Component;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.jackrabbit.JcrConstants;
036
037import org.ametys.plugins.repository.RepositoryConstants;
038import org.ametys.plugins.repository.provider.AbstractRepository;
039import org.ametys.plugins.repositoryapp.jcr.VersionHistoryHelper;
040import org.ametys.plugins.repositoryapp.jcr.VersionHistoryHelper.RemoveHistoryResult;
041import org.ametys.runtime.plugin.component.AbstractLogEnabled;
042
043/**
044 * Helper to manipulate the version history
045 */
046public class ContentVersionHistoryHelper extends AbstractLogEnabled implements Component, Serviceable
047{
048    /** The avalon role */
049    public static final String ROLE = ContentVersionHistoryHelper.class.getName();
050    
051    /** The repository provider */
052    protected Repository _repository;
053
054    public void service(ServiceManager manager) throws ServiceException
055    {
056        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
057    }
058    
059    /**
060     * Remove the unused version history of contents of a given type
061     * @param contentType the id of a content type used to limit the targeted version history.
062     */
063    public void clearUnusedHistory(String contentType)
064    {
065        Session defaultSession = null;
066        Map<String, Session> sessions = null;
067        
068        try
069        {
070            defaultSession = _repository.login();
071            sessions = _initSessions(defaultSession);
072            String query = "//element(*, " + JcrConstants.NT_VERSIONHISTORY + ")";
073            if (StringUtils.isNotEmpty(contentType))
074            {
075                query = query + "[*/*/@" + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":" + DefaultContent.METADATA_CONTENTTYPE + "='" + contentType + "']";
076            }
077            QueryManager queryManager = defaultSession.getWorkspace().getQueryManager();
078            @SuppressWarnings("deprecation")
079            QueryResult queryResult = queryManager.createQuery(query, Query.XPATH).execute();
080            
081            NodeIterator nodes = queryResult.getNodes();
082            while (nodes.hasNext())
083            {
084                VersionHistory vh = (VersionHistory) nodes.nextNode();
085                
086                RemoveHistoryResult result = VersionHistoryHelper.removeUnusedHistory(sessions, vh, getLogger());
087                switch (result)
088                {
089                    case REFERENCED:
090                        getLogger().warn("Couldn't remove history of node '" + vh.getIdentifier() + "'. The versionable node is still referenced.");
091                        break;
092                    case UNFIXABLE:
093                        getLogger().warn("Couldn't remove history of node '" + vh.getIdentifier() + "'. The node couldn't be fixed before removal.");
094                        break;
095                    default:
096                    case REMOVED:
097                    case FIXED:
098                    case ERROR:
099                        break;
100                    
101                }
102            }
103        }
104        catch (RepositoryException e)
105        {
106            getLogger().error("Failed to clear the version history", e);
107        }
108        finally
109        {
110            if (sessions != null)
111            {
112                for (Session s: sessions.values())
113                {
114                    s.logout();
115                }
116            }
117            else if (defaultSession != null && defaultSession.isLive())
118            {
119                defaultSession.logout();
120            }
121        }
122    }
123
124    private Map<String, Session> _initSessions(Session defaultSession) throws RepositoryException
125    {
126        Map<String, Session> result = new HashMap<>();
127        try
128        {
129            String defaultWorkspaceName = defaultSession.getWorkspace().getName();
130            result.put(defaultWorkspaceName, defaultSession);
131            
132            for (String workspaceName : defaultSession.getWorkspace().getAccessibleWorkspaceNames())
133            {
134                if (!workspaceName.equals(defaultWorkspaceName))
135                {
136                    result.put(workspaceName, _repository.login(workspaceName));
137                }
138            }
139            
140            return result;
141        }
142        catch (Exception e)
143        {
144            for (Session s : result.values())
145            {
146                s.logout();
147            }
148            throw e;
149        }
150    }
151
152}