001/* 002 * Copyright 2012 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.odf.workflow; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import javax.jcr.RepositoryException; 029 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.environment.Redirector; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.ObservationConstants; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.repository.ModifiableWorkflowAwareContent; 038import org.ametys.cms.repository.WorkflowAwareContent; 039import org.ametys.cms.workflow.AbstractContentFunction; 040import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 041import org.ametys.cms.workflow.ContentWorkflowHelper; 042import org.ametys.core.observation.Event; 043import org.ametys.odf.ODFHelper; 044import org.ametys.odf.ProgramItem; 045import org.ametys.odf.course.Course; 046import org.ametys.odf.orgunit.OrgUnit; 047import org.ametys.odf.program.AbstractProgram; 048import org.ametys.plugins.repository.AmetysObjectResolver; 049import org.ametys.plugins.repository.AmetysRepositoryException; 050import org.ametys.plugins.repository.UnknownAmetysObjectException; 051import org.ametys.plugins.repository.version.VersionableAmetysObject; 052import org.ametys.plugins.workflow.AbstractWorkflowComponent; 053import org.ametys.runtime.i18n.I18nizableText; 054 055import com.opensymphony.module.propertyset.PropertySet; 056import com.opensymphony.workflow.InvalidActionException; 057import com.opensymphony.workflow.WorkflowException; 058 059/** 060 * OSWorkflow function for validating a ODF content. 061 * If argument "recursively" is used, the referenced contents will be validated too. 062 */ 063public class ValidateODFContentFunction extends AbstractContentFunction 064{ 065 /** Label for the validated version of contents. */ 066 public static final String VALID_LABEL = "Live"; 067 068 /** Label for the validated version of contents. */ 069 public static final String VALIDATE_RECURSIVELY_ARG = "recursively"; 070 071 /** Constant for storing the result map into the transient variables map. */ 072 public static final String INVALIDATED_CONTENTS_KEY = ValidateODFContentFunction.class.getName() + "-invalidatedContents"; 073 074 /** The action id of global validation */ 075 public static final int GLOBAL_VALIDATE_ACTION_ID = 900; 076 077 /** The action id of global validation */ 078 public static final int VALIDATE_ACTION_ID = 4; 079 080 /** The validate step id */ 081 public static final int VALIDATED_STEP_ID = 3; 082 083 /** The Ametys object resolver */ 084 protected AmetysObjectResolver _resolver; 085 /** The workflow helper for contents */ 086 protected ContentWorkflowHelper _contentWorkflowHelper; 087 /** The ODF helper */ 088 protected ODFHelper _odfHelper; 089 /** The ODF workflow helper */ 090 protected ODFWorkflowHelper _odfWorkflowHelper; 091 092 @Override 093 public void service(ServiceManager smanager) throws ServiceException 094 { 095 super.service(smanager); 096 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 097 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 098 _odfWorkflowHelper = (ODFWorkflowHelper) smanager.lookup(ODFWorkflowHelper.ROLE); 099 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 100 } 101 102 @Override 103 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 104 { 105 _logger.info("Performing content validation"); 106 107 WorkflowAwareContent content = getContent(transientVars); 108 109 if (!(content instanceof ModifiableWorkflowAwareContent)) 110 { 111 throw new IllegalArgumentException("The provided content " + content.getId() + " is not a ModifiableWorkflowAwareContent."); 112 } 113 114 ModifiableWorkflowAwareContent modifiableContent = (ModifiableWorkflowAwareContent) content; 115 116 try 117 { 118 _validateContent(modifiableContent); 119 120 if (args.containsKey(VALIDATE_RECURSIVELY_ARG)) 121 { 122 // Validate recursively the referenced contents 123 _validateRecursively(content, transientVars); 124 } 125 126 // Set the current step ID. 127 _setCurrentStepIdAndNotify(modifiableContent, transientVars); 128 // Create a new version. 129 _createVersion(modifiableContent); 130 // Add the valid label on the newly created version. 131 _addLabel(modifiableContent, VALID_LABEL); 132 133 if (_getInvalidatedContents(transientVars).size() > 0) 134 { 135 String invalidatedContents = StringUtils.join(_getInvalidatedContents(transientVars), ","); 136 addWorkflowWarning(transientVars, new I18nizableText("plugin.odf", "PLUGINS_ODF_WORKFLOW_ACTION_GLOBAL_VALIDATION_INVALIDATED", Collections.singletonList(invalidatedContents))); 137 } 138 } 139 catch (RepositoryException e) 140 { 141 throw new WorkflowException("Unable to link the workflow to the content", e); 142 } 143 catch (AmetysRepositoryException e) 144 { 145 throw new WorkflowException("Unable to validate the content", e); 146 } 147 148 _notifyObservers(transientVars, content); 149 } 150 151 /** 152 * Notify observers of content validation 153 * @param transientVars The transient variables 154 * @param content The created content 155 * @throws AmetysRepositoryException If an error occurred with the repository 156 * @throws WorkflowException If an error occurred with the workflow 157 */ 158 protected void _notifyObservers (Map transientVars, Content content) throws AmetysRepositoryException, WorkflowException 159 { 160 Map<String, Object> eventParams = new HashMap<>(); 161 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 162 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 163 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_VALIDATED, getUser(transientVars), eventParams)); 164 } 165 166 /** 167 * Validates the content. 168 * @param content the content. 169 * @throws WorkflowException if an error occurs. 170 * @throws RepositoryException if an error occurs. 171 */ 172 protected void _validateContent(ModifiableWorkflowAwareContent content) throws WorkflowException, RepositoryException 173 { 174 if (!(content instanceof VersionableAmetysObject)) 175 { 176 throw new WorkflowException("Invalid content implementation: " + content); 177 } 178 179 Date validationDate = new Date(); 180 boolean isValid = Arrays.asList(((VersionableAmetysObject) content).getAllLabels()).contains(VALID_LABEL); 181 if (!isValid) 182 { 183 content.setLastMajorValidationDate(validationDate); 184 } 185 content.setLastValidationDate(validationDate); 186 // Remove the proposal date. 187 content.setProposalDate(null); 188 189 content.saveChanges(); 190 } 191 192 /** 193 * Validate the referenced contents recursively 194 * @param content The validated content 195 * @param transientVars The parameters from the call. 196 * @throws WorkflowException if an error occurs 197 */ 198 protected void _validateRecursively (WorkflowAwareContent content, Map transientVars) throws WorkflowException 199 { 200 if (content instanceof ProgramItem) 201 { 202 // Validate the structure recursively 203 List<ProgramItem> children = _odfHelper.getChildProgramItems((ProgramItem) content); 204 for (ProgramItem child : children) 205 { 206 WorkflowAwareContent waChild = (WorkflowAwareContent) child; 207 if (!_odfWorkflowHelper.isInValidatedStep(waChild)) 208 { 209 _doValidateWorkflowAction((WorkflowAwareContent) child, transientVars, GLOBAL_VALIDATE_ACTION_ID); 210 } 211 else 212 { 213 // The program item is already validated. Continue to its own children. 214 _validateRecursively(waChild, transientVars); 215 } 216 } 217 } 218 219 // Validate others referenced contents 220 if (content instanceof AbstractProgram) 221 { 222 _validateReferencedContents(((AbstractProgram) content).getOrgUnits(), transientVars); 223 _validateReferencedContents(((AbstractProgram) content).getContacts(), transientVars); 224 225 Set<String> persons = new HashSet<>(); 226 Map<String, String[]> personsInChargeByRole = ((AbstractProgram) content).getPersonsInCharge(); 227 for (String role : personsInChargeByRole.keySet()) 228 { 229 for (String person : personsInChargeByRole.get(role)) 230 { 231 persons.add(person); 232 } 233 } 234 _validateReferencedContents(persons, transientVars); 235 } 236 else if (content instanceof Course) 237 { 238 _validateReferencedContents(((Course) content).getOrgUnits(), transientVars); 239 _validateReferencedContents(((Course) content).getContacts(), transientVars); 240 _validateReferencedContents(((Course) content).getPersonsInCharge(), transientVars); 241 } 242 else if (content instanceof OrgUnit) 243 { 244 _validateReferencedContents(((OrgUnit) content).getContacts(), transientVars); 245 } 246 } 247 248 /** 249 * Validate the list of contents 250 * @param contentIds The id of contents to validate 251 * @param transientVars The parameters from the call. 252 * @throws WorkflowException if an error occurred 253 */ 254 protected void _validateReferencedContents (Collection<String> contentIds, Map transientVars) throws WorkflowException 255 { 256 for (String id : contentIds) 257 { 258 try 259 { 260 if (StringUtils.isNotEmpty(id)) 261 { 262 WorkflowAwareContent content = _resolver.resolveById(id); 263 if (!_odfWorkflowHelper.isInValidatedStep(content)) 264 { 265 _doValidateWorkflowAction (content, transientVars, VALIDATE_ACTION_ID); 266 } 267 } 268 } 269 catch (UnknownAmetysObjectException e) 270 { 271 // Nothing 272 } 273 } 274 } 275 276 /** 277 * Validate a content 278 * @param content The content to validate 279 * @param transientVars The parameters from the call. 280 * @param actionId The id of validate action 281 * @throws WorkflowException if an error occurred 282 */ 283 @SuppressWarnings("unchecked") 284 protected void _doValidateWorkflowAction (WorkflowAwareContent content, Map transientVars, int actionId) throws WorkflowException 285 { 286 try 287 { 288 Map<String, Object> result = _contentWorkflowHelper.doAction(content, actionId, _getInputs(content, transientVars)); 289 if (result.containsKey(INVALIDATED_CONTENTS_KEY)) 290 { 291 _getInvalidatedContents(transientVars).addAll((Set<String>) result.get(INVALIDATED_CONTENTS_KEY)); 292 } 293 } 294 catch (InvalidActionException e) 295 { 296 _getInvalidatedContents(transientVars).add(content.getTitle()); 297 _logger.warn("Unable to validate content " + content.getTitle() + " (" + content.getName() + ") : mandatory metadata are probably missing"); 298 } 299 } 300 301 /** 302 * Get the contents that were not validated during global validation 303 * @param transientVars The parameters from the call. 304 * @return The unvalidated contents 305 * @throws WorkflowException if failed to get invalidated contents 306 */ 307 @SuppressWarnings("unchecked") 308 protected Set<String> _getInvalidatedContents(Map transientVars) throws WorkflowException 309 { 310 if (!getResultsMap(transientVars).containsKey(INVALIDATED_CONTENTS_KEY)) 311 { 312 getResultsMap(transientVars).put(INVALIDATED_CONTENTS_KEY, new HashSet<String>()); 313 } 314 315 return (Set<String>) getResultsMap(transientVars).get(INVALIDATED_CONTENTS_KEY); 316 } 317 318 /** 319 * Get inputs 320 * @param content The content to validate 321 * @param transientVars the parameters from the call. 322 * @return The inputs 323 */ 324 protected Map<String, Object> _getInputs (Content content, Map transientVars) 325 { 326 Map<String, Object> inputs = new HashMap<>(); 327 328 // Provide the redirector 329 Redirector redirector = (Redirector) transientVars.get(Redirector.class.getName()); 330 inputs.put(Redirector.class.getName(), redirector); 331 332 // Provide the content key 333 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 334 335 // Provide a map for providing data to the generator 336 Map<String, Object> result = new HashMap<>(); 337 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, result); 338 339 return inputs; 340 } 341 342}