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.impl.ModelAwareComposite;
036import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeater;
037import org.ametys.plugins.repository.data.holder.group.impl.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        try
074        {
075            Content content = getContent(transientVars);
076            List<String> conditionFailures = getConditionFailures(transientVars);
077            
078            List<ModelItem> modelItems = content.getModel().stream().flatMap(model -> model.getModelItems().stream()).collect(Collectors.toList());
079            
080            if (!_validateValues(modelItems, Optional.of(content), "", content, conditionFailures))
081            {
082                return false;
083            }
084            
085            String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
086            for (String cTypeId : allContentTypes)
087            {
088                ContentType contentType = _cTypeEP.getExtension(cTypeId);
089                
090                for (ContentValidator contentValidator : contentType.getGlobalValidators())
091                {
092                    Errors errors = new Errors();
093                    contentValidator.validate(content, errors);
094                    if (errors.hasErrors())
095                    {
096                        List<String> translatedErrors = errors.getErrors().stream().map(error -> _i18nUtils.translate(error, "en")).collect(Collectors.toList());
097                        
098                        conditionFailures.add(String.format("Validate condition failed for content '%s' on global validator '%s' with errors %s", content.getId(), contentValidator.getClass().getName(), translatedErrors));
099                        return false;
100                    }
101                }
102            }
103            
104            return true;
105        }
106        catch (AmetysRepositoryException e)
107        {
108            getConditionFailures(transientVars).add("Validate metadata condition failed (" + e.getMessage() + ")");
109            _logger.error("Cannot check condition ("  + e.getMessage() + "). Assuming it is false.", e);
110            return false; 
111        }
112    }
113    
114    private boolean _validateValues(List<ModelItem> modelItems, Optional<? extends ModelAwareDataHolder> dataHolder, String dataPath, Content content, List<String> conditionFailures)
115    {
116        for (ModelItem modelItem : modelItems)
117        {
118            String name = modelItem.getName();
119            if (modelItem instanceof ElementDefinition)
120            {
121                // simple element
122                ElementDefinition definition = (ElementDefinition) modelItem;
123                Object value = dataHolder.map(holder -> holder.getValue(name)).orElse(null);
124                
125                if (!ModelHelper.validateValue(definition, value).isEmpty())
126                {
127                    conditionFailures.add(String.format("Validate attribute condition failed for content %s on attribute %s with value %s", content.getId(), dataPath + name, String.valueOf(value)));
128                    return false;
129                }
130            }
131            else if (modelItem instanceof CompositeDefinition)
132            {
133                // composite
134                Optional<ModelAwareComposite> composite = dataHolder.map(holder -> holder.getComposite(name));
135                if (!_validateValues(((CompositeDefinition) modelItem).getChildren(), composite, dataPath + name + "/", content, conditionFailures))
136                {
137                    return false;
138                }
139            }
140            else if (modelItem instanceof RepeaterDefinition)
141            {
142                // repeater
143                RepeaterDefinition definition = (RepeaterDefinition) modelItem;
144                Optional<ModelAwareRepeater> repeater = dataHolder.map(holder -> holder.getRepeater(name));
145                
146                Optional<List<? extends ModelAwareRepeaterEntry>> entries = repeater.map(ModelAwareRepeater::getEntries);
147                
148                int repeaterSize = repeater.map(ModelAwareRepeater::getSize).orElse(0);
149                int minSize = definition.getMinSize();
150                int maxSize = definition.getMaxSize();
151                
152                if (repeaterSize < minSize
153                    || maxSize > 0 && repeaterSize > maxSize)
154                {
155                    conditionFailures.add(String.format("Validate repeater size condition failed for content %s on repeater %s with size %s", content.getId(), dataPath + name, repeaterSize));
156                    return false;
157                }
158                
159                if (entries.isPresent())
160                {
161                    for (ModelAwareRepeaterEntry entry : entries.get())
162                    {
163                        if (!_validateValues(definition.getChildren(), Optional.of(entry), dataPath + name + "[" + (entry.getPosition())  + "]/", content, conditionFailures))
164                        {
165                            return false;
166                        }
167                    }
168                }
169            }
170        }
171        
172        return true;
173    }
174}