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}