001/* 002 * Copyright 2010 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.List; 019import java.util.Map; 020import java.util.Optional; 021import java.util.stream.Collectors; 022 023import org.apache.avalon.framework.activity.Initializable; 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.commons.lang3.ArrayUtils; 027 028import org.ametys.cms.contenttype.ContentType; 029import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 030import org.ametys.cms.contenttype.ContentValidator; 031import org.ametys.cms.repository.Content; 032import org.ametys.core.util.I18nUtils; 033import org.ametys.plugins.repository.AmetysRepositoryException; 034import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 035import org.ametys.plugins.repository.data.holder.group.ModelAwareComposite; 036import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater; 037import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry; 038import org.ametys.plugins.repository.model.CompositeDefinition; 039import org.ametys.plugins.repository.model.RepeaterDefinition; 040import org.ametys.runtime.model.ElementDefinition; 041import org.ametys.runtime.model.ModelHelper; 042import org.ametys.runtime.model.ModelItem; 043import org.ametys.runtime.parameter.Errors; 044 045import com.opensymphony.module.propertyset.PropertySet; 046import com.opensymphony.workflow.Condition; 047import com.opensymphony.workflow.WorkflowException; 048 049/** 050 * OSWorkflow condition to check all content metadata are valid 051 */ 052public class ValidateContentCondition extends AbstractContentWorkflowComponent implements Condition, Initializable 053{ 054 private ContentTypeExtensionPoint _cTypeEP; 055 private I18nUtils _i18nUtils; 056 057 @Override 058 public void initialize() throws Exception 059 { 060 _cTypeEP = (ContentTypeExtensionPoint) _manager.lookup(ContentTypeExtensionPoint.ROLE); 061 } 062 063 @Override 064 public void service(ServiceManager smanager) throws ServiceException 065 { 066 super.service(smanager); 067 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 068 } 069 070 @Override 071 public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException 072 { 073 List<String> conditionFailures = getConditionFailures(transientVars); 074 try 075 { 076 Content content = getContent(transientVars); 077 078 List<ModelItem> modelItems = ModelHelper.getModelItems(content.getModel()) 079 .stream() 080 .filter(modelItem -> !(modelItem instanceof ElementDefinition) || ((ElementDefinition) modelItem).isEditable()) 081 .collect(Collectors.toList()); 082 083 if (!_validateValues(modelItems, Optional.of(content), "", content, conditionFailures)) 084 { 085 return false; 086 } 087 088 String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 089 for (String cTypeId : allContentTypes) 090 { 091 ContentType contentType = _cTypeEP.getExtension(cTypeId); 092 093 for (ContentValidator contentValidator : contentType.getGlobalValidators()) 094 { 095 Errors errors = new Errors(); 096 contentValidator.validate(content, errors); 097 if (errors.hasErrors()) 098 { 099 List<String> translatedErrors = errors.getErrors().stream().map(error -> _i18nUtils.translate(error, "en")).collect(Collectors.toList()); 100 101 conditionFailures.add(String.format("Validate condition failed for content '%s' on global validator '%s' with errors %s", content.getId(), contentValidator.getClass().getName(), translatedErrors)); 102 return false; 103 } 104 } 105 } 106 107 return true; 108 } 109 catch (AmetysRepositoryException e) 110 { 111 conditionFailures.add("Validate metadata condition failed (" + e.getMessage() + ")"); 112 _logger.error("Cannot check condition (" + e.getMessage() + "). Assuming it is false.", e); 113 return false; 114 } 115 } 116 117 private boolean _validateValues(List<ModelItem> modelItems, Optional<? extends ModelAwareDataHolder> dataHolder, String dataPath, Content content, List<String> conditionFailures) 118 { 119 for (ModelItem modelItem : modelItems) 120 { 121 String name = modelItem.getName(); 122 if (modelItem instanceof ElementDefinition) 123 { 124 // simple element 125 ElementDefinition definition = (ElementDefinition) modelItem; 126 Object value = dataHolder.map(holder -> holder.getValue(name)).orElse(null); 127 128 if (!ModelHelper.validateValue(definition, value).isEmpty()) 129 { 130 conditionFailures.add(String.format("Validate attribute condition failed for content %s on attribute %s with value %s", content.getId(), dataPath + name, String.valueOf(value))); 131 return false; 132 } 133 } 134 else if (modelItem instanceof CompositeDefinition) 135 { 136 // composite 137 Optional<ModelAwareComposite> composite = dataHolder.map(holder -> holder.getComposite(name)); 138 if (!_validateValues(((CompositeDefinition) modelItem).getChildren(), composite, dataPath + name + "/", content, conditionFailures)) 139 { 140 return false; 141 } 142 } 143 else if (modelItem instanceof RepeaterDefinition) 144 { 145 // repeater 146 RepeaterDefinition definition = (RepeaterDefinition) modelItem; 147 Optional<ModelAwareRepeater> repeater = dataHolder.map(holder -> holder.getRepeater(name)); 148 149 Optional<List<? extends ModelAwareRepeaterEntry>> entries = repeater.map(ModelAwareRepeater::getEntries); 150 151 int repeaterSize = repeater.map(ModelAwareRepeater::getSize).orElse(0); 152 int minSize = definition.getMinSize(); 153 int maxSize = definition.getMaxSize(); 154 155 if (repeaterSize < minSize 156 || maxSize > 0 && repeaterSize > maxSize) 157 { 158 conditionFailures.add(String.format("Validate repeater size condition failed for content %s on repeater %s with size %s", content.getId(), dataPath + name, repeaterSize)); 159 return false; 160 } 161 162 if (entries.isPresent()) 163 { 164 for (ModelAwareRepeaterEntry entry : entries.get()) 165 { 166 if (!_validateValues(definition.getChildren(), Optional.of(entry), dataPath + name + "[" + (entry.getPosition()) + "]/", content, conditionFailures)) 167 { 168 return false; 169 } 170 } 171 } 172 } 173 } 174 175 return true; 176 } 177}