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.workflow;
017
018import java.util.Map;
019
020import javax.jcr.Node;
021import javax.jcr.NodeIterator;
022import javax.jcr.RepositoryException;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.collections.PredicateUtils;
027
028import org.ametys.cms.repository.CloneComponent;
029import org.ametys.cms.repository.Content;
030import org.ametys.cms.repository.WorkflowAwareContent;
031import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollection;
032import org.ametys.plugins.repository.AmetysObjectResolver;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.repository.version.VersionableAmetysObject;
035import org.ametys.runtime.i18n.I18nizableText;
036
037import com.opensymphony.module.propertyset.PropertySet;
038import com.opensymphony.workflow.WorkflowException;
039
040/**
041 * Restore the content of attachments from a previous version.
042 * This function is intended to be call after the {@link EditContentFunction}
043 * 
044 * @implNote this implementation only support root of type {@link JCRResourcesCollection}
045 */
046public class RestoreContentAttachmentsFunction extends AbstractContentFunction
047{
048    private CloneComponent _cloneComponent;
049    private AmetysObjectResolver _resolver;
050    
051    @Override
052    public void service(ServiceManager manager) throws ServiceException
053    {
054        super.service(manager);
055        _cloneComponent = (CloneComponent) manager.lookup(CloneComponent.ROLE);
056        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
057    }
058    
059    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
060    {
061        Map<String, Object> resultsMap = getResultsMap(transientVars);
062        // Only restore the attachments if all went well with the edit
063        if (EditContentFunction.RESULT_STATE_OK.equals(resultsMap.get(EditContentFunction.RESULT_STATE_KEY)))
064        {
065            WorkflowAwareContent content = getContent(transientVars);
066            
067            String contentVersion = (String) getContextParameters(transientVars).get("contentVersion");
068            
069            Content oldContent = _resolver.resolveById(content.getId());
070            if (oldContent instanceof VersionableAmetysObject versionAware)
071            {
072                versionAware.switchToRevision(contentVersion);
073            }
074            
075            if (content.getRootAttachments() instanceof JCRResourcesCollection currentRootAttachments)
076            {
077                try
078                {
079                    boolean hasChanged = false;
080                    if (oldContent.getRootAttachments() instanceof JCRResourcesCollection oldRootAttachments)
081                    {
082                        _cloneComponent.cloneNodeAndPreserveUUID(
083                                oldRootAttachments.getNode(),
084                                currentRootAttachments.getNode(),
085                                PredicateUtils.truePredicate(),
086                                PredicateUtils.truePredicate());
087                        hasChanged = currentRootAttachments.getNode().getSession().hasPendingChanges();
088                    }
089                    // in the case where the old content don't have a root
090                    // just delete the children of the current (but keep the root)
091                    else if (oldContent.getRootAttachments() == null)
092                    {
093                        Node node = currentRootAttachments.getNode();
094                        
095                        NodeIterator subNodes = node.getNodes();
096                        while (subNodes.hasNext())
097                        {
098                            hasChanged = true;
099                            subNodes.nextNode().remove();
100                        }
101                    }
102                    else
103                    {
104                        _logger.warn("Failed to restore attachments on " + content.getId() + ". Restoring attachments from a root other than a JCRResourceCollection is not supported. The attachments were left untouched.");
105                    }
106                    
107                    if (hasChanged)
108                    {
109                        resultsMap.put(HAS_CHANGED_KEY, true);
110                    }
111                }
112                catch (RepositoryException e)
113                {
114                    throw new AmetysRepositoryException(e);
115                }
116            }
117            else if (content.getRootAttachments() == null
118                    && oldContent.getRootAttachments() == null)
119            {
120                // if the current root is "empty" and the previous one was too.
121                // Do nothing.
122            }
123            else
124            {
125                _logger.warn("Failed to restore attachments on " + content.getId() + ". Restoring attachments on a root other than a JCRResourceCollection is not supported. The attachments were left untouched.");
126            }
127        }
128    }
129    
130    public I18nizableText getLabel()
131    {
132        return new I18nizableText("plugin.cms", "PLUGINS_CMS_RESTORE_CONTENT_ATTACHMENTS_FUNCTION_LABEL");
133    }
134}