001/* 002 * Copyright 2020 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.ArrayList; 019import java.util.Date; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027 028import org.ametys.cms.data.ContentValue; 029import org.ametys.cms.data.type.ModelItemTypeConstants; 030import org.ametys.cms.repository.Content; 031import org.ametys.cms.repository.ModifiableWorkflowAwareContent; 032import org.ametys.cms.repository.WorkflowAwareContent; 033import org.ametys.core.user.UserIdentity; 034import org.ametys.plugins.repository.AmetysObjectResolver; 035import org.ametys.plugins.repository.data.external.ExternalizableDataProviderExtensionPoint; 036import org.ametys.plugins.repository.lock.LockHelper; 037import org.ametys.plugins.repository.lock.LockableAmetysObject; 038import org.ametys.plugins.repository.model.RepositoryDataContext; 039import org.ametys.plugins.repository.version.VersionableAmetysObject; 040import org.ametys.runtime.model.ElementDefinition; 041import org.ametys.runtime.model.View; 042import org.ametys.runtime.model.ViewItemContainer; 043 044import com.opensymphony.module.propertyset.PropertySet; 045import com.opensymphony.workflow.WorkflowException; 046 047/** 048 * OSWorkflow function to restore an old revision of a content. 049 * Builds a Map with the old content's attributes values, and passes it to the 050 * {@link EditContentFunction}, which does the real job. 051 */ 052public class RestoreRevisionFunction extends AbstractContentFunction 053{ 054 private AmetysObjectResolver _resolver; 055 private ExternalizableDataProviderExtensionPoint _externalizableDataProviderEP; 056 057 @Override 058 public void service(ServiceManager manager) throws ServiceException 059 { 060 super.service(manager); 061 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 062 _externalizableDataProviderEP = (ExternalizableDataProviderExtensionPoint) manager.lookup(ExternalizableDataProviderExtensionPoint.ROLE); 063 } 064 065 @Override 066 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 067 { 068 WorkflowAwareContent content = getContent(transientVars); 069 UserIdentity user = getUser(transientVars); 070 071 if (!(content instanceof ModifiableWorkflowAwareContent)) 072 { 073 throw new IllegalArgumentException("The provided content " + content.getId() + " is not a ModifiableWorkflowAwareContent."); 074 } 075 076 ModifiableWorkflowAwareContent modifiableContent = (ModifiableWorkflowAwareContent) content; 077 078 if (content instanceof LockableAmetysObject) 079 { 080 LockableAmetysObject lockableContent = (LockableAmetysObject) content; 081 if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, user)) 082 { 083 throw new WorkflowException ("The user '" + user + "' try to restore the content '" + content.getId() + "', but this content is locked by the user '" + user + "'"); 084 } 085 else if (lockableContent.isLocked()) 086 { 087 lockableContent.unlock(); 088 } 089 } 090 091 String contentVersion = (String) getContextParameters(transientVars).get("contentVersion"); 092 093 Content oldContent = _resolver.resolveById(content.getId()); 094 if (oldContent instanceof VersionableAmetysObject) 095 { 096 ((VersionableAmetysObject) oldContent).switchToRevision(contentVersion); 097 } 098 099 Map<String, Object> results = getResultsMap(transientVars); 100 Map<String, Object> brokenReferences = new HashMap<>(); 101 results.put("brokenReferences", brokenReferences); 102 103 Map<String, Object> parameters = getContextParameters(transientVars); 104 Set<String> externalizableData = _externalizableDataProviderEP.getExternalizableDataPaths(content); 105 RepositoryDataContext context = RepositoryDataContext.newInstance().withExternalizableData(externalizableData); 106 Map<String, Object> newValues = oldContent.dataToMap(context); 107 108 View view = View.of(content.getModel()); 109 newValues = _processContents(view, newValues, "", brokenReferences); 110 111 parameters.put(EditContentFunction.VIEW, view); 112 parameters.put(EditContentFunction.VALUES_KEY, newValues); 113 parameters.put(EditContentFunction.QUIT, Boolean.TRUE); 114 115 modifiableContent.setLastContributor(user); 116 modifiableContent.setLastModified(new Date()); 117 118 // Remove the proposal date. 119 modifiableContent.setProposalDate(null); 120 121 // Commit changes 122 modifiableContent.saveChanges(); 123 } 124 125 private ContentValue _processContentValue(ContentValue value, String dataPath, ElementDefinition definition, Map<String, Object> brokenReferences) 126 { 127 if (_resolver.hasAmetysObjectForId(value.getContentId())) 128 { 129 return value; 130 } 131 else if (!brokenReferences.containsKey(dataPath)) 132 { 133 brokenReferences.put(dataPath, definition.getLabel()); 134 } 135 136 return null; 137 } 138 139 @SuppressWarnings("unchecked") 140 private Map<String, Object> _processContents(ViewItemContainer viewItemContainer, Map<String, Object> values, String dataPath, Map<String, Object> brokenReferences) 141 { 142 if (values == null) 143 { 144 return null; 145 } 146 147 Map<String, Object> result = new HashMap<>(); 148 149 org.ametys.plugins.repository.model.ViewHelper.visitView(viewItemContainer, 150 (element, definition) -> { 151 // simple element 152 String name = definition.getName(); 153 Object value = values.get(name); 154 if (definition.getType().getId().equals(ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID)) 155 { 156 Object newValue = null; 157 if (value instanceof ContentValue[]) 158 { 159 List<ContentValue> validContents = new ArrayList<>(); 160 161 for (ContentValue contentValue : (ContentValue[]) value) 162 { 163 ContentValue newContentValue = _processContentValue(contentValue, dataPath + name, definition, brokenReferences); 164 if (newContentValue != null) 165 { 166 validContents.add(newContentValue); 167 } 168 } 169 170 newValue = validContents; 171 } 172 else if (value instanceof ContentValue) 173 { 174 ContentValue newContentValue = _processContentValue((ContentValue) value, dataPath + name, definition, brokenReferences); 175 newValue = newContentValue; 176 } 177 178 result.put(name, newValue); 179 } 180 else 181 { 182 result.put(name, value); 183 } 184 }, 185 (group, definition) -> { 186 // composite 187 String name = definition.getName(); 188 if (values.containsKey(name)) 189 { 190 result.put(name, _processContents(group, (Map<String, Object>) values.get(name), dataPath + name + "/" , brokenReferences)); 191 } 192 }, 193 (group, definition) -> { 194 // repeater 195 String name = definition.getName(); 196 if (values.containsKey(name)) 197 { 198 List<Map<String, Object>> entries = (List<Map<String, Object>>) values.get(name); 199 200 List<Map<String, Object>> newEntries = null; 201 if (entries != null) 202 { 203 newEntries = new ArrayList<>(); 204 205 for (int i = 0; i < entries.size(); i++) 206 { 207 newEntries.add(_processContents(group, entries.get(i), dataPath + name + "[" + (i + 1) + "]/" , brokenReferences)); 208 } 209 } 210 211 result.put(name, newEntries); 212 } 213 }, 214 group -> result.putAll(_processContents(group, values, dataPath, brokenReferences))); 215 216 return result; 217 } 218}