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 186 content.setLastValidationDate(validationDate); 187 if (content.getFirstValidationDate() == null) 188 { 189 content.setFirstValidationDate(validationDate); 190 } 191 192 // Remove the proposal date. 193 content.setProposalDate(null); 194 195 content.saveChanges(); 196 } 197 198 /** 199 * Validate the referenced contents recursively 200 * @param content The validated content 201 * @param transientVars The parameters from the call. 202 * @throws WorkflowException if an error occurs 203 */ 204 protected void _validateRecursively (WorkflowAwareContent content, Map transientVars) throws WorkflowException 205 { 206 if (content instanceof ProgramItem) 207 { 208 // Validate the structure recursively 209 List<ProgramItem> children = _odfHelper.getChildProgramItems((ProgramItem) content); 210 for (ProgramItem child : children) 211 { 212 WorkflowAwareContent waChild = (WorkflowAwareContent) child; 213 if (!_odfWorkflowHelper.isInValidatedStep(waChild)) 214 { 215 _doValidateWorkflowAction((WorkflowAwareContent) child, transientVars, GLOBAL_VALIDATE_ACTION_ID); 216 } 217 else 218 { 219 // The program item is already validated. Continue to its own children. 220 _validateRecursively(waChild, transientVars); 221 } 222 } 223 } 224 225 // Validate others referenced contents 226 if (content instanceof AbstractProgram) 227 { 228 _validateReferencedContents(((AbstractProgram) content).getOrgUnits(), transientVars); 229 _validateReferencedContents(((AbstractProgram) content).getContacts(), transientVars); 230 231 Set<String> persons = new HashSet<>(); 232 Map<String, String[]> personsInChargeByRole = ((AbstractProgram) content).getPersonsInCharge(); 233 for (String role : personsInChargeByRole.keySet()) 234 { 235 for (String person : personsInChargeByRole.get(role)) 236 { 237 persons.add(person); 238 } 239 } 240 _validateReferencedContents(persons, transientVars); 241 } 242 else if (content instanceof Course) 243 { 244 _validateReferencedContents(((Course) content).getOrgUnits(), transientVars); 245 _validateReferencedContents(((Course) content).getContacts(), transientVars); 246 _validateReferencedContents(((Course) content).getPersonsInCharge(), transientVars); 247 } 248 else if (content instanceof OrgUnit) 249 { 250 _validateReferencedContents(((OrgUnit) content).getContacts(), transientVars); 251 } 252 } 253 254 /** 255 * Validate the list of contents 256 * @param contentIds The id of contents to validate 257 * @param transientVars The parameters from the call. 258 * @throws WorkflowException if an error occurred 259 */ 260 protected void _validateReferencedContents (Collection<String> contentIds, Map transientVars) throws WorkflowException 261 { 262 for (String id : contentIds) 263 { 264 try 265 { 266 if (StringUtils.isNotEmpty(id)) 267 { 268 WorkflowAwareContent content = _resolver.resolveById(id); 269 if (!_odfWorkflowHelper.isInValidatedStep(content)) 270 { 271 _doValidateWorkflowAction (content, transientVars, VALIDATE_ACTION_ID); 272 } 273 } 274 } 275 catch (UnknownAmetysObjectException e) 276 { 277 // Nothing 278 } 279 } 280 } 281 282 /** 283 * Validate a content 284 * @param content The content to validate 285 * @param transientVars The parameters from the call. 286 * @param actionId The id of validate action 287 * @throws WorkflowException if an error occurred 288 */ 289 @SuppressWarnings("unchecked") 290 protected void _doValidateWorkflowAction (WorkflowAwareContent content, Map transientVars, int actionId) throws WorkflowException 291 { 292 try 293 { 294 Map<String, Object> result = _contentWorkflowHelper.doAction(content, actionId, _getInputs(content, transientVars)); 295 if (result.containsKey(INVALIDATED_CONTENTS_KEY)) 296 { 297 _getInvalidatedContents(transientVars).addAll((Set<String>) result.get(INVALIDATED_CONTENTS_KEY)); 298 } 299 } 300 catch (InvalidActionException e) 301 { 302 _getInvalidatedContents(transientVars).add(content.getTitle()); 303 _logger.warn("Unable to validate content " + content.getTitle() + " (" + content.getName() + ") : mandatory metadata are probably missing"); 304 } 305 } 306 307 /** 308 * Get the contents that were not validated during global validation 309 * @param transientVars The parameters from the call. 310 * @return The unvalidated contents 311 * @throws WorkflowException if failed to get invalidated contents 312 */ 313 @SuppressWarnings("unchecked") 314 protected Set<String> _getInvalidatedContents(Map transientVars) throws WorkflowException 315 { 316 if (!getResultsMap(transientVars).containsKey(INVALIDATED_CONTENTS_KEY)) 317 { 318 getResultsMap(transientVars).put(INVALIDATED_CONTENTS_KEY, new HashSet<String>()); 319 } 320 321 return (Set<String>) getResultsMap(transientVars).get(INVALIDATED_CONTENTS_KEY); 322 } 323 324 /** 325 * Get inputs 326 * @param content The content to validate 327 * @param transientVars the parameters from the call. 328 * @return The inputs 329 */ 330 protected Map<String, Object> _getInputs (Content content, Map transientVars) 331 { 332 Map<String, Object> inputs = new HashMap<>(); 333 334 // Provide the redirector 335 Redirector redirector = (Redirector) transientVars.get(Redirector.class.getName()); 336 inputs.put(Redirector.class.getName(), redirector); 337 338 // Provide the content key 339 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 340 341 // Provide a map for providing data to the generator 342 Map<String, Object> result = new HashMap<>(); 343 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, result); 344 345 return inputs; 346 } 347 348}