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